Remove frontend source from repository.

This commit is contained in:
James Cole
2022-01-29 14:01:17 +01:00
parent aff0e4a9df
commit acb2a8697a
135 changed files with 0 additions and 29134 deletions

View File

@@ -1,92 +0,0 @@
<!--
- AssetAccountRole.vue
- 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/>.
-->
<template>
<div class="form-group">
<div class="text-xs d-none d-lg-block d-xl-block">
{{ $t('form.account_role') }}
</div>
<div class="input-group" v-if="loading">
<span class="fas fa-spinner fa-spin"></span>
</div>
<div class="input-group" v-if="!loading">
<select
ref="account_role"
v-model="account_role"
:class="errors.length > 0 ? 'form-control is-invalid' : 'form-control'"
:title="$t('form.account_role')"
autocomplete="off"
name="account_role"
:disabled=disabled
>
<option v-for="role in this.roleList" :label="role.title" :value="role.slug">{{ role.title }}</option>
</select>
</div>
<span v-if="errors.length > 0">
<span v-for="error in errors" class="text-danger small">{{ error }}<br/></span>
</span>
</div>
</template>
<script>
export default {
name: "AssetAccountRole",
props: {
value: {},
errors: {},
disabled: {
type: Boolean,
default: false
},
},
data() {
return {
roleList: [],
account_role: this.value,
loading: false
}
},
methods: {
loadRoles: function () {
//
axios.get('./api/v1/configuration/firefly.accountRoles')
.then(response => {
let content = response.data.data.value;
for (let i in content) {
if (content.hasOwnProperty(i)) {
let current = content[i];
this.roleList.push({slug: current, title: this.$t('firefly.account_role_' + current)})
}
}
}
);
}
},
watch: {
account_role: function (value) {
this.$emit('set-field', {field: 'account_role', value: value});
},
},
created() {
this.loadRoles()
}
}
</script>

View File

@@ -1,397 +0,0 @@
<!--
- Create.vue
- 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/>.
-->
<template>
<div>
<Alert :message="errorMessage" type="danger"/>
<Alert :message="successMessage" type="success"/>
<form @submit="submitForm" autocomplete="off">
<div class="row">
<div class="col-xl-6 col-lg-6 col-md-12 col-sm-12 col-xs-12">
<div class="card card-primary">
<div class="card-header">
<h3 class="card-title">
{{ $t('firefly.mandatoryFields') }}
</h3>
</div>
<div class="card-body">
<GenericTextInput :disabled="submitting" v-model="name" field-name="name" :errors="errors.name" :title="$t('form.name')"
v-on:set-field="storeField($event)"/>
<GenericCurrency :disabled="submitting" v-model="currency_id" :errors="errors.currency_id" v-on:set-field="storeField($event)"/>
<AssetAccountRole :disabled="submitting" v-if="'asset' === type" v-model="account_role" :errors="errors.account_role"
v-on:set-field="storeField($event)"/>
<!-- some CC fields -->
<CreditCardType :disabled="submitting" v-if="'ccAsset' === account_role" v-model="credit_card_type" :errors="errors.credit_card_type"
v-on:set-field="storeField($event)" />
<GenericTextInput :disabled="submitting" v-if="'ccAsset' === account_role" field-type="date" v-model="monthly_payment_date" field-name="monthly_payment_date"
:errors="errors.monthly_payment_date" :title="$t('form.cc_monthly_payment_date')" v-on:set-field="storeField($event)" />
<!-- liability fields -->
<LiabilityType :disabled="submitting" v-if="'liabilities' === type" v-model="liability_type" :errors="errors.liability_type"
v-on:set-field="storeField($event)"/>
<LiabilityDirection :disabled="submitting" v-if="'liabilities' === type" v-model="liability_direction" :errors="errors.liability_direction"
v-on:set-field="storeField($event)"/>
<GenericTextInput :disabled="submitting" v-if="'liabilities' === type" field-type="number" field-step="any" v-model="liability_amount"
field-name="liability_amount" :errors="errors.liability_amount" :title="$t('form.amount')" v-on:set-field="storeField($event)"/>
<GenericTextInput :disabled="submitting" v-if="'liabilities' === type" field-type="date" v-model="liability_date" field-name="liability_date"
:errors="errors.liability_date" :title="$t('form.date')" v-on:set-field="storeField($event)"/>
<Interest :disabled="submitting" v-if="'liabilities' === type" v-model="interest" :errors="errors.interest" v-on:set-field="storeField($event)"/>
<InterestPeriod :disabled="submitting" v-if="'liabilities' === type" v-model="interest_period" :errors="errors.interest_period"
v-on:set-field="storeField($event)"/>
</div>
</div>
</div>
<div class="col-xl-6 col-lg-6 col-md-12 col-sm-12 col-xs-12">
<div class="card">
<div class="card-header">
<h3 class="card-title">
{{ $t('firefly.optionalFields') }}
</h3>
</div>
<div class="card-body">
<GenericTextInput :disabled="submitting" v-model="iban" field-name="iban" :errors="errors.iban" :title="$t('form.iban')"
v-on:set-field="storeField($event)"/>
<GenericTextInput :disabled="submitting" v-model="bic" field-name="bic" :errors="errors.bic" :title="$t('form.BIC')"
v-on:set-field="storeField($event)"/>
<GenericTextInput :disabled="submitting" v-model="account_number" field-name="account_number" :errors="errors.account_number"
:title="$t('form.account_number')" v-on:set-field="storeField($event)"/>
<GenericTextInput :disabled="submitting" v-if="'asset' === type" field-type="amount" v-model="virtual_balance" field-name="virtual_balance"
:errors="errors.virtual_balance" :title="$t('form.virtual_balance')" v-on:set-field="storeField($event)"/>
<GenericTextInput :disabled="submitting" v-if="'asset' === type" field-type="amount" v-model="opening_balance" field-name="opening_balance"
:errors="errors.opening_balance" :title="$t('form.opening_balance')" v-on:set-field="storeField($event)"/>
<GenericTextInput :disabled="submitting" v-if="'asset' === type" field-type="date" v-model="opening_balance_date"
field-name="opening_balance_date" :errors="errors.opening_balance_date" :title="$t('form.opening_balance_date')"
v-on:set-field="storeField($event)"/>
<GenericCheckbox :disabled="submitting" v-if="'asset' === type" :title="$t('form.include_net_worth')" field-name="include_net_worth"
v-model="include_net_worth" :errors="errors.include_net_worth" :description="$t('form.include_net_worth')"
v-on:set-field="storeField($event)"/>
<GenericCheckbox :disabled="submitting" :title="$t('form.active')" field-name="active"
v-model="active" :errors="errors.active" :description="$t('form.active')"
v-on:set-field="storeField($event)"/>
<GenericTextarea :disabled="submitting" field-name="notes" :title="$t('form.notes')" v-model="notes" :errors="errors.notes"
v-on:set-field="storeField($event)"/>
<GenericLocation :disabled="submitting" v-model="location" :title="$t('form.location')" :errors="errors.location"
v-on:set-field="storeField($event)"/>
<!-- attachments -->
<GenericAttachments :disabled="submitting" :title="$t('form.attachments')" field-name="attachments" :errors="errors.attachments"
v-on:selected-attachments="selectedAttachments($event)"
v-on:selected-no-attachments="selectedNoAttachments($event)"
v-on:uploaded-attachments="uploadedAttachments($event)"
:upload-trigger="uploadTrigger"
:upload-object-type="uploadObjectType"
:upload-object-id="uploadObjectId"
/>
</div>
</div>
</div>
</div>
</form>
<div class="row">
<div class="col-xl-6 col-lg-6 col-md-12 col-sm-12 col-xs-12 offset-xl-6 offset-lg-6">
<div class="card">
<div class="card-body">
<div class="row">
<div class="col-lg-6 offset-lg-6">
<button :disabled=submitting type="button" @click="submitForm" class="btn btn-success btn-block">{{
$t('firefly.store_new_' + type + '_account')
}}
</button>
<div class="form-check">
<input id="createAnother" v-model="createAnother" class="form-check-input" type="checkbox">
<label class="form-check-label" for="createAnother">
<span class="small">{{ $t('firefly.create_another') }}</span>
</label>
</div>
<div class="form-check">
<input id="resetFormAfter" v-model="resetFormAfter" :disabled="!createAnother" class="form-check-input" type="checkbox">
<label class="form-check-label" for="resetFormAfter">
<span class="small">{{ $t('firefly.reset_after') }}</span>
</label>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import format from "date-fns/format";
const lodashClonedeep = require('lodash.clonedeep');
import GenericCurrency from "../form/GenericCurrency";
import AssetAccountRole from "./AssetAccountRole"
import LiabilityType from "./LiabilityType";
import CreditCardType from "./CreditCardType";
import LiabilityDirection from "./LiabilityDirection";
import Interest from "./Interest";
import InterestPeriod from "./InterestPeriod";
import GenericTextInput from "../form/GenericTextInput";
import GenericTextarea from "../form/GenericTextarea";
import GenericLocation from "../form/GenericLocation";
import GenericAttachments from "../form/GenericAttachments";
import GenericCheckbox from "../form/GenericCheckbox";
import Alert from '../partials/Alert';
export default {
name: "Create",
components: {
GenericCurrency, AssetAccountRole, LiabilityType, LiabilityDirection, Interest, InterestPeriod,
GenericTextInput, GenericTextarea, GenericLocation, GenericAttachments, GenericCheckbox, Alert,
CreditCardType
},
created() {
this.errors = lodashClonedeep(this.defaultErrors);
let pathName = window.location.pathname;
let parts = pathName.split('/');
this.type = parts[parts.length - 1];
this.date = format(new Date, 'yyyy-MM-dd');
this.monthly_payment_date = format(new Date, 'yyyy-MM-dd');
},
data() {
return {
submitting: false,
successMessage: '',
errorMessage: '',
createAnother: false,
resetFormAfter: false,
returnedId: 0,
returnedTitle: '',
// info
name: '',
type: 'any',
currency_id: null,
// liabilities
liability_type: 'Loan',
liability_direction: 'debit',
liability_amount: null,
liability_date: null,
interest: null,
interest_period: 'monthly',
// optional fields
iban: null,
bic: null,
account_number: null,
virtual_balance: null,
opening_balance: null,
opening_balance_date: null,
include_net_worth: true,
active: true,
notes: null,
location: {},
// credit card fields
monthly_payment_date: null,
credit_card_type: 'monthlyFull',
// has attachments to upload?
hasAttachments: false,
uploadTrigger: false,
uploadObjectId: 0,
uploadObjectType: 'Account',
account_role: 'defaultAsset',
errors: {
currency_id: [],
credit_card_type: [],
},
defaultErrors: {
name: [],
monthly_payment_date: [],
currency_id: [],
account_role: [],
liability_type: [],
liability_direction: [],
liability_amount: [],
credit_card_type: [],
liability_date: [],
interest: [],
interest_period: [],
iban: [],
bic: [],
account_number: [],
virtual_balance: [],
opening_balance: [],
opening_balance_date: [],
include_net_worth: [],
notes: [],
location: [],
}
}
},
methods: {
storeField: function (payload) {
// console.log(payload);
if ('location' === payload.field) {
if (true === payload.value.hasMarker) {
this.location = payload.value;
return;
}
this.location = {};
return;
}
this[payload.field] = payload.value;
},
selectedAttachments: function (e) {
this.hasAttachments = true;
},
selectedNoAttachments: function (e) {
this.hasAttachments = false;
},
uploadedAttachments: function (e) {
// console.log('Response to event uploaded-attachments');
// console.log(e);
this.finishSubmission();
},
submitForm: function (e) {
e.preventDefault();
this.submitting = true;
let submission = this.getSubmission();
// console.log('Will submit:');
// console.log(submission);
let url = './api/v1/accounts';
axios.post(url, submission)
.then(response => {
this.errors = lodashClonedeep(this.defaultErrors);
this.returnedId = parseInt(response.data.data.id);
this.returnedTitle = response.data.data.attributes.name;
if (this.hasAttachments) {
// upload attachments. Do a callback to a finish up method.
this.uploadObjectId = this.returnedId;
this.uploadTrigger = true;
}
if (!this.hasAttachments) {
this.finishSubmission();
}
})
.catch(error => {
this.submitting = false;
this.parseErrors(error.response.data);
});
},
finishSubmission: function () {
this.successMessage = this.$t('firefly.stored_new_account_js', {ID: this.returnedId, name: this.returnedTitle});
// stay here is false?
if (false === this.createAnother) {
window.location.href = (window.previousURL ?? '/') + '?account_id=' + this.returnedId + '&message=created';
return;
}
this.submitting = false;
if (this.resetFormAfter) {
// console.log('reset!');
this.name = '';
this.liability_type = 'Loan';
this.liability_direction = 'debit';
this.liability_amount = null;
this.liability_date = null;
this.interest = null;
this.interest_period = 'monthly';
this.iban = null;
this.bic = null;
this.account_number = null;
this.virtual_balance = null;
this.opening_balance = null;
this.opening_balance_date = null;
this.include_net_worth = true;
this.active = true;
this.notes = null;
this.location = {};
}
},
parseErrors: function (errors) {
this.errors = lodashClonedeep(this.defaultErrors);
// console.log(errors);
for (let i in errors.errors) {
if (errors.errors.hasOwnProperty(i)) {
this.errors[i] = errors.errors[i];
}
if ('liability_start_date' === i) {
this.errors.opening_balance_date = errors.errors[i];
}
}
},
getSubmission: function () {
let submission = {
"name": this.name,
"type": this.type,
"iban": this.iban,
"bic": this.bic,
"account_number": this.account_number,
"currency_id": this.currency_id,
"virtual_balance": this.virtual_balance,
"active": this.active,
"order": 31337,
"include_net_worth": this.include_net_worth,
"account_role": this.account_role,
"notes": this.notes,
};
if ('liabilities' === this.type) {
submission.liability_type = this.liability_type.toLowerCase();
submission.interest = this.interest;
submission.interest_period = this.interest_period;
submission.liability_amount = this.liability_amount;
submission.liability_start_date = this.liability_date;
submission.liability_direction = this.liability_direction;
}
if ((null !== this.opening_balance || null !== this.opening_balance_date) && 'asset' === this.type) {
submission.opening_balance = this.opening_balance;
submission.opening_balance_date = this.opening_balance_date;
}
if ('' === submission.opening_balance) {
delete submission.opening_balance;
}
if ('asset' === this.type && 'ccAsset' === this.account_role) {
submission.credit_card_type = this.credit_card_type;
submission.monthly_payment_date = this.monthly_payment_date;
}
if (Object.keys(this.location).length >= 3) {
submission.longitude = this.location.lng;
submission.latitude = this.location.lat;
submission.zoom_level = this.location.zoomLevel;
}
return submission;
}
}
}
</script>

View File

@@ -1,97 +0,0 @@
<!--
- LiabilityType.vue
- 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/>.
-->
<template>
<div class="form-group">
<div class="text-xs d-none d-lg-block d-xl-block">
{{ $t('form.cc_type') }}
</div>
<div class="input-group" v-if="loading">
<span class="fas fa-spinner fa-spin"></span>
</div>
<div class="input-group" v-if="!loading">
<select
ref="credit_card_type"
v-model="credit_card_type"
:class="errors.length > 0 ? 'form-control is-invalid' : 'form-control'"
:title="$t('form.cc_type')"
autocomplete="off"
name="credit_card_type"
:disabled=disabled
>
<option v-for="type in this.typeList" :label="type.title" :value="type.slug">{{ type.title }}</option>
</select>
</div>
<span v-if="errors.length > 0">
<span v-for="error in errors" class="text-danger small">{{ error }}<br/></span>
</span>
</div>
</template>
<script>
export default {
name: "CreditCardType",
props: {
value: {},
errors: {},
disabled: {
type: Boolean,
default: false
},
},
data() {
return {
typeList: [],
credit_card_type: this.value,
loading: true
}
},
methods: {
loadRoles: function () {
//
axios.get('./api/v1/configuration/firefly.credit_card_types')
.then(response => {
let content = response.data.data.value;
for (let i in content) {
if (content.hasOwnProperty(i)) {
let current = content[i];
this.typeList.push({slug: current, title: this.$t('firefly.credit_card_type_' + current)})
}
}
this.loading = false;
}
);
}
},
watch: {
credit_card_type: function (value) {
this.$emit('set-field', {field: 'credit_card_type', value: value});
},
value: function(value) {
this.credit_card_type = value;
}
},
created() {
this.loadRoles()
}
}
</script>

View File

@@ -1,188 +0,0 @@
<!--
- Delete.vue
- 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/>.
-->
<template>
<div>
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<div class="card card-default card-danger">
<div class="card-header">
<h3 class="card-title">
<span class="fas fa-exclamation-triangle"></span>
{{ $t('firefly.delete_account') }}
</h3>
</div>
<!-- /.card-header -->
<div class="card-body">
<div class="callout callout-danger" v-if="!deleting && !deleted">
<p>
<span class="far fa-dizzy"></span> {{ $t('form.permDeleteWarning') }}
</p>
</div>
<p v-if="!loading && !deleting && !deleted">
{{ $t('form.account_areYouSure_js', {'name': this.accountName}) }}
</p>
<p v-if="!loading && !deleting && !deleted">
<span v-if="piggyBankCount > 0">
{{ $tc('form.also_delete_piggyBanks_js', piggyBankCount, {count: piggyBankCount}) }}
</span>
<span v-if="transactionCount > 0">
{{ $tc('form.also_delete_transactions_js', transactionCount, {count: transactionCount}) }}
</span>
</p>
<p v-if="transactionCount > 0 && !deleting && !deleted">
{{ $tc('firefly.save_transactions_by_moving_js', transactionCount) }}
</p>
<p v-if="transactionCount > 0 && !deleting && !deleted">
<select name="account" v-model="moveToAccount" class="form-control">
<option :label="$t('firefly.none_in_select_list')" :value="0">{{ $t('firefly.none_in_select_list') }}</option>
<option v-for="account in accounts" :label="account.name" :value="account.id">{{ account.name }}</option>
</select>
</p>
<p v-if="loading || deleting || deleted" class="text-center">
<span class="fas fa-spinner fa-spin"></span>
</p>
</div>
<div class="card-footer">
<button @click="deleteAccount" class="btn btn-danger float-right" v-if="!loading && !deleting && !deleted"> {{
$t('form.deletePermanently')
}}
</button>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "Delete",
data() {
return {
loading: true,
deleting: false,
deleted: false,
accountId: 0,
accountName: '',
piggyBankCount: 0,
transactionCount: 0,
moveToAccount: 0,
accounts: []
}
},
created() {
let pathName = window.location.pathname;
let parts = pathName.split('/');
this.accountId = parseInt(parts[parts.length - 1]);
this.getAccount();
},
methods: {
deleteAccount: function () {
this.deleting = true;
if (0 === this.moveToAccount) {
this.execDeleteAccount();
}
if (0 !== this.moveToAccount) {
// move to another account:
this.moveTransactions();
}
},
moveTransactions: function () {
let query =
{
where: {account_id: this.accountId},
update: {account_id: this.moveToAccount}
};
axios.post('./api/v1/data/bulk/transactions', {query: JSON.stringify(query)}).then(response => {
this.execDeleteAccount();
});
},
execDeleteAccount: function () {
axios.delete('./api/v1/accounts/' + this.accountId)
.then(response => {
this.deleted = true;
this.deleting = false;
window.location.href = (window.previousURL ?? '/') + '?account_id=' + this.accountId + '&message=deleted';
});
},
getAccount: function () {
axios.get('./api/v1/accounts/' + this.accountId)
.then(response => {
let account = response.data.data;
this.accountName = account.attributes.name;
// now get piggy and transaction count
this.getPiggyBankCount(account.attributes.type, account.attributes.currency_code);
}
);
},
getAccounts: function (type, currencyCode) {
axios.get('./api/v1/accounts?type=' + type)
.then(response => {
let accounts = response.data.data;
for (let i in accounts) {
if (accounts.hasOwnProperty(i) && /^0$|^[1-9]\d*$/.test(i) && i <= 4294967294) {
let current = accounts[i];
if (false === current.attributes.active) {
continue;
}
if (currencyCode !== current.attributes.currency_code) {
continue;
}
if (this.accountId === parseInt(current.id)) {
continue;
}
this.accounts.push({id: current.id, name: current.attributes.name});
}
}
this.loading = false;
}
);
// get accounts of the same type.
// console.log('Go for "' + type + '"');
},
getPiggyBankCount: function (type, currencyCode) {
axios.get('./api/v1/accounts/' + this.accountId + '/piggy_banks')
.then(response => {
this.piggyBankCount = response.data.meta.pagination.total ? parseInt(response.data.meta.pagination.total) : 0;
this.getTransactionCount(type, currencyCode);
}
);
},
getTransactionCount: function (type, currencyCode) {
axios.get('./api/v1/accounts/' + this.accountId + '/transactions')
.then(response => {
this.transactionCount = response.data.meta.pagination.total ? parseInt(response.data.meta.pagination.total) : 0;
if (this.transactionCount > 0) {
this.getAccounts(type, currencyCode);
}
if (0 === this.transactionCount) {
this.loading = false;
}
}
);
}
}
}
</script>

View File

@@ -1,388 +0,0 @@
<!--
- Edit.vue
- Copyright (c) 2020 james@firefly-iii.org
-
- This file is part of Firefly III (https://github.com/firefly-iii).
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div>
<Alert :message="errorMessage" type="danger"/>
<Alert :message="successMessage" type="success"/>
<form @submit="submitForm" autocomplete="off">
<div class="row">
<div class="col-xl-6 col-lg-6 col-md-12 col-sm-12 col-xs-12">
<div class="card card-primary">
<div class="card-header">
<h3 class="card-title">
{{ $t('firefly.mandatoryFields') }}
</h3>
</div>
<div class="card-body">
<GenericTextInput :disabled="submitting" v-model="account.name" field-name="name" :errors="errors.name" :title="$t('form.name')"
v-on:set-field="storeField($event)"/>
<GenericCurrency :disabled="submitting" v-model="account.currency_id" :errors="errors.currency_id" v-on:set-field="storeField($event)"/>
<AssetAccountRole :disabled="submitting" v-if="'asset' === account.type" v-model="account.account_role" :errors="errors.account_role"
v-on:set-field="storeField($event)"/>
<LiabilityType :disabled="submitting" v-if="'liabilities' === account.type" v-model="account.liability_type" :errors="errors.liability_type"
v-on:set-field="storeField($event)"/>
<LiabilityDirection :disabled="submitting" v-if="'liabilities' === account.type" v-model="account.liability_direction"
:errors="errors.liability_direction"
v-on:set-field="storeField($event)"/>
<GenericTextInput :disabled="submitting" v-if="'liabilities' === account.type" field-type="number" field-step="any"
v-model="account.liability_amount"
field-name="liability_amount" :errors="errors.liability_amount" :title="$t('form.amount')" v-on:set-field="storeField($event)"/>
<GenericTextInput :disabled="submitting" v-if="'liabilities' === account.type" field-type="date" v-model="account.liability_date"
field-name="liability_date"
:errors="errors.liability_date" :title="$t('form.date')" v-on:set-field="storeField($event)"/>
<Interest :disabled="submitting" v-if="'liabilities' === account.type" v-model="account.interest" :errors="errors.interest"
v-on:set-field="storeField($event)"/>
<InterestPeriod :disabled="submitting" v-if="'liabilities' === account.type" v-model="account.interest_period" :errors="errors.interest_period"
v-on:set-field="storeField($event)"/>
</div>
</div>
</div>
<div class="col-xl-6 col-lg-6 col-md-12 col-sm-12 col-xs-12">
<div class="card">
<div class="card-header">
<h3 class="card-title">
{{ $t('firefly.optionalFields') }}
</h3>
</div>
<div class="card-body">
<GenericTextInput :disabled="submitting" v-model="account.iban" field-name="iban" :errors="errors.iban" :title="$t('form.iban')"
v-on:set-field="storeField($event)"/>
<GenericTextInput :disabled="submitting" v-model="account.bic" field-name="bic" :errors="errors.bic" :title="$t('form.BIC')"
v-on:set-field="storeField($event)"/>
<GenericTextInput :disabled="submitting" v-model="account.account_number" field-name="account_number" :errors="errors.account_number"
:title="$t('form.account_number')" v-on:set-field="storeField($event)"/>
<GenericTextInput :disabled="submitting" v-if="'asset' === account.type" field-type="amount" v-model="account.virtual_balance"
field-name="virtual_balance"
:errors="errors.virtual_balance" :title="$t('form.virtual_balance')" v-on:set-field="storeField($event)"/>
<GenericTextInput :disabled="submitting" v-if="'asset' === account.type" field-type="amount" v-model="account.opening_balance"
field-name="opening_balance"
:errors="errors.opening_balance" :title="$t('form.opening_balance')" v-on:set-field="storeField($event)"/>
<GenericTextInput :disabled="submitting" v-if="'asset' === account.type" field-type="date" v-model="account.opening_balance_date"
field-name="opening_balance_date" :errors="errors.opening_balance_date" :title="$t('form.opening_balance_date')"
v-on:set-field="storeField($event)"/>
<GenericCheckbox :disabled="submitting" v-if="'asset' === account.type" :title="$t('form.include_net_worth')" field-name="include_net_worth"
v-model="account.include_net_worth" :errors="errors.include_net_worth" :description="$t('form.include_net_worth')"
v-on:set-field="storeField($event)"/>
<GenericCheckbox :disabled="submitting" :title="$t('form.active')" field-name="active"
v-model="account.active" :errors="errors.active" :description="$t('form.active')"
v-on:set-field="storeField($event)"/>
<GenericTextarea :disabled="submitting" field-name="notes" :title="$t('form.notes')" v-model="account.notes" :errors="errors.notes"
v-on:set-field="storeField($event)"/>
<GenericLocation :disabled="submitting" v-model="account.location" :title="$t('form.location')" :errors="errors.location"
v-on:set-field="storeField($event)"/>
<GenericAttachments :disabled="submitting" :title="$t('form.attachments')" field-name="attachments" :errors="errors.attachments"
v-on:selected-attachments="selectedAttachments($event)"
v-on:selected-no-attachments="selectedNoAttachments($event)"
v-on:uploaded-attachments="uploadedAttachments($event)"
:upload-trigger="uploadTrigger"
:upload-object-type="uploadObjectType"
:upload-object-id="uploadObjectId"
/>
</div>
</div>
</div>
</div>
</form>
<div class="row">
<div class="col-xl-6 col-lg-6 col-md-12 col-sm-12 col-xs-12 offset-xl-6 offset-lg-6">
<div class="card">
<div class="card-body">
<div class="row">
<div class="col-lg-6 offset-lg-6">
<button :disabled=submitting type="button" @click="submitForm" class="btn btn-success btn-block">{{
$t('firefly.update_' + account.type + '_account')
}}
</button>
<div class="form-check">
<input id="stayHere" v-model="stayHere" class="form-check-input" type="checkbox">
<label class="form-check-label" for="stayHere">
<span class="small">{{ $t('firefly.after_update_create_another') }}</span>
</label>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import Alert from '../partials/Alert';
import lodashClonedeep from "lodash.clonedeep";
import GenericTextInput from '../form/GenericTextInput';
import GenericCurrency from "../form/GenericCurrency";
import AssetAccountRole from "./AssetAccountRole";
import LiabilityType from "./LiabilityType";
import LiabilityDirection from "./LiabilityDirection";
import Interest from "./Interest";
import InterestPeriod from "./InterestPeriod";
import GenericTextarea from "../form/GenericTextarea";
import GenericCheckbox from "../form/GenericCheckbox";
import GenericAttachments from "../form/GenericAttachments";
import GenericLocation from "../form/GenericLocation";
export default {
name: "Edit",
created() {
// console.log('Created');
let parts = window.location.pathname.split('/');
this.accountId = parseInt(parts[parts.length - 1]);
this.uploadObjectId = parseInt(parts[parts.length - 1]);
this.getAccount();
},
components: {
Alert,
GenericTextInput,
GenericCurrency,
AssetAccountRole,
LiabilityDirection,
LiabilityType,
Interest,
InterestPeriod,
GenericTextarea,
GenericCheckbox,
GenericAttachments,
GenericLocation
},
data() {
return {
successMessage: '',
errorMessage: '',
stayHere: false,
inError: false,
accountId: 0,
submitting: false,
// account + original account
account: {},
originalAccount: {},
// has attachments to upload?
hasAttachments: false,
uploadTrigger: false,
uploadObjectId: 0,
uploadObjectType: 'Account',
// errors
errors: {
currency_id: [],
account_role: [],
liability_type: [],
location: []
},
defaultErrors: {
name: [],
currency_id: [],
account_role: [],
liability_type: [],
liability_direction: [],
liability_amount: [],
liability_date: [],
interest: [],
interest_period: [],
iban: [],
bic: [],
account_number: [],
virtual_balance: [],
opening_balance: [],
opening_balance_date: [],
include_net_worth: [],
active: [],
notes: [],
location: [],
attachments: [],
}
}
},
methods: {
selectedAttachments: function (e) {
this.hasAttachments = true;
},
selectedNoAttachments: function (e) {
this.hasAttachments = false;
},
uploadedAttachments: function (e) {
this.finaliseSubmission();
},
submitForm: function (e) {
e.preventDefault();
this.submitting = true;
let submission = this.getSubmission();
if (0 === Object.keys(submission).length) {
// console.log('Nothing to submit. Just finish up.');
this.finaliseSubmission();
return;
}
// console.log('Will submit:');
// console.log(submission);
const url = './api/v1/accounts/' + this.accountId;
axios.put(url, submission)
.then(this.processSubmission)
.catch(err => {
this.handleSubmissionError(err.response.data)
});
},
processSubmission: function() {
if (this.hasAttachments) {
// upload attachments. Do a callback to a finish up method.
this.uploadTrigger = true;
return;
}
this.finaliseSubmission();
},
finaliseSubmission: function () {
// console.log('finaliseSubmission');
// stay here, display message
if (true === this.stayHere && false === this.inError) {
this.errorMessage = '';
this.successMessage = this.$t('firefly.updated_account_js', {ID: this.accountId, title: this.account.name});
this.submitting = false;
}
// return to previous (bad hack), display message:
if (false === this.stayHere && false === this.inError) {
//console.log('no error + changes + redirect');
window.location.href = (window.previousURL ?? '/') + '?account_id=' + this.accountId + '&message=updated';
this.submitting = false;
}
// error or warning? here.
// console.log('end of finaliseSubmission');
},
handleSubmissionError: function (errors) {
console.error('Bad');
console.error(errors);
this.inError = true;
this.submitting = false;
this.errors = lodashClonedeep(this.defaultErrors);
for (let i in errors.errors) {
if (errors.errors.hasOwnProperty(i)) {
this.errors[i] = errors.errors[i];
}
}
},
getSubmission: function () {
let submission = {};
// console.log('getSubmission');
// console.log(this.account);
for (let i in this.account) {
// console.log(i);
if (this.account.hasOwnProperty(i) && this.originalAccount.hasOwnProperty(i) && JSON.stringify(this.account[i]) !== JSON.stringify(this.originalAccount[i])) {
// console.log('Field "' + i + '" has changed.');
// console.log('Original:')
// console.log(this.account[i]);
// console.log('Backup : ');
// console.log(this.originalAccount[i]);
submission[i] = this.account[i];
}
// else {
// console.log('Field "' + i + '" has not changed.');
// }
}
return submission;
},
/**
* Grab account from URL and submit GET.
*/
getAccount: function () {
// console.log('getTransactionGroup');
axios.get('./api/v1/accounts/' + this.accountId)
.then(response => {
this.parseAccount(response.data);
}
).catch(error => {
console.error('I failed :(');
console.error(error);
});
},
storeField: function (payload) {
//console.log(payload);
if ('location' === payload.field) {
if (true === payload.value.hasMarker) {
this.account.location = payload.value;
return;
}
this.account.location = {};
return;
}
this.account[payload.field] = payload.value;
},
/**
* Parse transaction group. Title is easy, transactions have their own method.
* @param response
*/
parseAccount: function (response) {
// console.log('Will now parse');
// console.log(response);
let attributes = response.data.attributes;
let account = {};
// parse account:
account.account_number = attributes.account_number;
account.account_role = attributes.account_role;
account.active = attributes.active;
account.bic = attributes.bic;
account.credit_card_type = attributes.credit_card_type;
account.currency_code = attributes.currency_code;
account.currency_decimal_places = parseInt(attributes.currency_decimal_places);
account.currency_id = parseInt(attributes.currency_id);
account.currency_symbol = attributes.currency_symbol;
account.iban = attributes.iban;
account.include_net_worth = attributes.include_net_worth;
account.interest = attributes.interest;
account.interest_period = attributes.interest_period;
account.liability_direction = attributes.liability_direction;
account.liability_type = attributes.liability_type;
account.monthly_payment_date = attributes.monthly_payment_date;
account.name = attributes.name;
account.notes = attributes.notes;
account.opening_balance = attributes.opening_balance;
account.opening_balance_date = attributes.opening_balance_date;
account.type = attributes.type;
account.virtual_balance = attributes.virtual_balance;
account.location = {};
if (null !== attributes.latitude && null !== attributes.longitude && null !== attributes.zoom_level) {
account.location = {
latitude: attributes.latitude,
longitude: attributes.longitude,
zoom_level: attributes.zoom_level
};
}
this.account = account;
this.originalAccount = lodashClonedeep(this.account);
},
}
}
</script>

View File

@@ -1,611 +0,0 @@
<!--
- Index.vue
- Copyright (c) 2020 james@firefly-iii.org
-
- This file is part of Firefly III (https://github.com/firefly-iii).
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div>
<div class="row">
<div class="col-lg-8 col-md-6 col-sm-12 col-xs-12">
<b-pagination
v-model="currentPage"
:total-rows="total"
:per-page="perPage"
aria-controls="my-table"
></b-pagination>
</div>
<div class="col-lg-4 col-md-6 col-sm-12 col-xs-12">
<a :href="'./accounts/create/' + type" class="btn btn-sm mb-2 float-right btn-success" :title="$t('firefly.create_new_' + type)"><span class="fas fa-plus"></span> {{ $t('firefly.create_new_' + type) }}</a>
<button @click="newCacheKey" class="btn btn-sm mb-2 mr-2 float-right btn-info"><span class="fas fa-sync"></span></button>
</div>
</div>
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<div class="card">
<div class="card-body p-0">
<b-table id="my-table" striped hover responsive="md" primary-key="id" :no-local-sorting="false"
:items="accounts" :fields="fields"
:per-page="perPage"
sort-icon-left
ref="table"
:current-page="currentPage"
:busy.sync="loading"
:sort-by.sync="sortBy"
:sort-desc.sync="sortDesc"
>
<template #table-busy>
<span class="fas fa-spinner fa-spin"></span>
</template>
<template #cell(name)="data">
<a :class="false === data.item.active ? 'text-muted' : ''" :href="'./accounts/show/' + data.item.id" :title="data.value">{{ data.value }}</a>
</template>
<template #cell(acct_number)="data">
{{ data.item.acct_number }}
</template>
<template #cell(last_activity)="data">
<span v-if="'asset' === type && 'loading' === data.item.last_activity">
<span class="fas fa-spinner fa-spin"></span>
</span>
<span v-if="'asset' === type && 'none' === data.item.last_activity" class="text-muted">
{{ $t('firefly.never') }}
</span>
<span v-if="'asset' === type && 'loading' !== data.item.last_activity && 'none' !== data.item.last_activity">
{{ data.item.last_activity }}
</span>
</template>
<template #cell(amount_due)="data">
<span class="text-success" v-if="parseFloat(data.item.amount_due) > 0">
{{ Intl.NumberFormat(locale, {style: 'currency', currency: data.item.currency_code}).format(data.item.amount_due) }}
</span>
<span class="text-danger" v-if="parseFloat(data.item.amount_due) < 0">
{{ Intl.NumberFormat(locale, {style: 'currency', currency: data.item.currency_code}).format(data.item.amount_due) }}
</span>
<span class="text-muted" v-if="parseFloat(data.item.amount_due) === 0.0">
{{ Intl.NumberFormat(locale, {style: 'currency', currency: data.item.currency_code}).format(data.item.amount_due) }}
</span>
</template>
<template #cell(current_balance)="data">
<span class="text-success" v-if="parseFloat(data.item.current_balance) > 0">
{{
Intl.NumberFormat(locale, {
style: 'currency', currency:
data.item.currency_code
}).format(data.item.current_balance)
}}
</span>
<span class="text-danger" v-if="parseFloat(data.item.current_balance) < 0">
{{
Intl.NumberFormat(locale, {
style: 'currency', currency:
data.item.currency_code
}).format(data.item.current_balance)
}}
</span>
<span class="text-muted" v-if="0 === parseFloat(data.item.current_balance)">
{{
Intl.NumberFormat(locale, {
style: 'currency', currency:
data.item.currency_code
}).format(data.item.current_balance)
}}
</span>
<span v-if="'asset' === type && 'loading' === data.item.balance_diff">
<span class="fas fa-spinner fa-spin"></span>
</span>
<span v-if="'asset' === type && 'loading' !== data.item.balance_diff">
(<span class="text-success" v-if="parseFloat(data.item.balance_diff) > 0">{{
Intl.NumberFormat(locale, {
style: 'currency', currency:
data.item.currency_code
}).format(data.item.balance_diff)
}}</span><span class="text-muted" v-if="0===parseFloat(data.item.balance_diff)">{{
Intl.NumberFormat(locale, {
style: 'currency', currency:
data.item.currency_code
}).format(data.item.balance_diff)
}}</span><span class="text-danger" v-if="parseFloat(data.item.balance_diff) < 0">{{
Intl.NumberFormat(locale, {
style: 'currency', currency:
data.item.currency_code
}).format(data.item.balance_diff)
}}</span>)
</span>
</template>
<template #cell(interest)="data">
{{ parseFloat(data.item.interest) }}% ({{ data.item.interest_period }})
</template>
<template #cell(menu)="data">
<div class="btn-group btn-group-sm">
<div class="dropdown">
<button class="btn btn-light btn-sm dropdown-toggle" type="button" :id="'dropdownMenuButton' + data.item.id" data-toggle="dropdown"
aria-haspopup="true" aria-expanded="false">
{{ $t('firefly.actions') }}
</button>
<div class="dropdown-menu" :aria-labelledby="'dropdownMenuButton' + data.item.id">
<a class="dropdown-item" :href="'./accounts/edit/' + data.item.id"><span class="fa fas fa-pencil-alt"></span> {{ $t('firefly.edit') }}</a>
<a class="dropdown-item" :href="'./accounts/delete/' + data.item.id"><span class="fa far fa-trash"></span> {{ $t('firefly.delete') }}</a>
<a v-if="'asset' === type" class="dropdown-item" :href="'./accounts/reconcile/' + data.item.id + '/index'"><span
class="fas fa-check"></span>
{{ $t('firefly.reconcile_this_account') }}</a>
</div>
</div>
</div>
</template>
</b-table>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-8 col-md-6 col-sm-12 col-xs-12">
<b-pagination
v-model="currentPage"
:total-rows="total"
:per-page="perPage"
aria-controls="my-table"
></b-pagination>
</div>
<div class="col-lg-4 col-md-6 col-sm-12 col-xs-12">
<a :href="'./accounts/create/' + type" class="btn btn-sm mt-2 float-right btn-success" :title="$t('firefly.create_new_' + type)"><span class="fas fa-plus"></span> {{ $t('firefly.create_new_' + type) }}</a>
<button @click="newCacheKey" class="btn btn-sm mt-2 mr-2 float-right btn-info"><span class="fas fa-sync"></span></button>
</div>
</div>
</div>
</template>
<script>
import {mapGetters, mapMutations} from "vuex";
import Sortable from "sortablejs";
import format from "date-fns/format";
import {configureAxios} from "../../shared/forageStore";
export default {
name: "Index",
props: {
accountTypes: String
},
data() {
return {
accounts: [],
allAccounts: [],
type: 'all',
downloaded: false,
loading: false,
ready: false,
fields: [],
currentPage: 1,
perPage: 5,
total: 1,
sortBy: 'order',
sortDesc: false,
api: null,
sortableOptions: {
disabled: false,
chosenClass: 'is-selected',
onEnd: null
},
sortable: null,
locale: 'en-US'
}
},
watch: {
start: function () {
this.getAccountList();
},
end: function () {
this.getAccountList();
},
orderMode: function (value) {
// update the table headers
this.updateFieldList();
// reorder the accounts:
this.reorderAccountList(value);
// make table sortable:
this.makeTableSortable(value);
},
activeFilter: function (value) {
this.filterAccountList();
}
},
computed: {
...mapGetters('root', ['listPageSize', 'cacheKey']),
...mapGetters('accounts/index', ['orderMode', 'activeFilter']),
...mapGetters('dashboard/index', ['start', 'end',]),
'indexReady': function () {
return null !== this.start && null !== this.end && null !== this.listPageSize && this.ready;
},
cardTitle: function () {
return this.$t('firefly.' + this.type + '_accounts');
}
},
created() {
this.locale = localStorage.locale ?? 'en-US';
let pathName = window.location.pathname;
let parts = pathName.split('/');
this.type = parts[parts.length - 1];
this.perPage = this.listPageSize ?? 51;
// console.log('Per page: ' + this.perPage);
let params = new URLSearchParams(window.location.search);
this.currentPage = params.get('page') ? parseInt(params.get('page')) : 1;
this.updateFieldList();
this.ready = true;
// make object thing:
// let token = document.head.querySelector('meta[name="csrf-token"]');
// this.api = setup(
// {
// // `axios` options
// //baseURL: './',
// headers: {'X-CSRF-TOKEN': token.content, 'X-James': 'yes'},
//
// // `axios-cache-adapter` options
// cache: {
// maxAge: 15 * 60 * 1000,
// readHeaders: false,
// exclude: {
// query: false,
// },
// debug: true
// }
// });
},
methods: {
...mapMutations('root', ['refreshCacheKey',]),
// itemsProvider: function (ctx, callback) {
// console.log('itemsProvider()');
// console.log('ctx.currentPage = ' + ctx.currentPage);
// console.log('this.currentPage = ' + this.currentPage);
// if (ctx.currentPage === this.currentPage) {
// let direction = this.sortDesc ? '-' : '+';
// let url = 'api/v1/accounts?type=' + this.type + '&page=' + ctx.currentPage + '&sort=' + direction + this.sortBy;
// this.api.get(url)
// .then(async (response) => {
// this.total = parseInt(response.data.meta.pagination.total);
// let items = this.parseAccountsAndReturn(response.data.data);
// items = this.filterAccountListAndReturn(items);
// callback(items);
// }
// );
// return null;
// }
// return [];
// },
saveAccountSort: function (event) {
let oldIndex = parseInt(event.oldIndex);
let newIndex = parseInt(event.newIndex);
let identifier = parseInt(event.item.attributes.getNamedItem('data-pk').value);
for (let i in this.accounts) {
if (this.accounts.hasOwnProperty(i) && /^0$|^[1-9]\d*$/.test(i) && i <= 4294967294) {
let current = this.accounts[i];
// the actual account
if (current.id === identifier) {
let newOrder = parseInt(current.order) + (newIndex - oldIndex);
this.accounts[i].order = newOrder;
let url = './api/v1/accounts/' + current.id;
axios.put(url, {order: newOrder}).then(response => {
// See reference nr. 8
this.getAccountList();
});
}
}
}
},
reorderAccountList: function (orderMode) {
if (orderMode) {
this.sortBy = 'order';
this.sortDesc = false;
}
},
newCacheKey: function () {
this.refreshCacheKey();
this.downloaded = false;
this.accounts = [];
this.getAccountList();
},
makeTableSortable: function (orderMode) {
this.sortableOptions.disabled = !orderMode;
this.sortableOptions.onEnd = this.saveAccountSort;
// make sortable of table:
if (null === this.sortable) {
this.sortable = Sortable.create(this.$refs.table.$el.querySelector('tbody'), this.sortableOptions);
}
this.sortable.option('disabled', this.sortableOptions.disabled);
},
updateFieldList: function () {
this.fields = [];
this.fields = [{key: 'name', label: this.$t('list.name'), sortable: !this.orderMode}];
if ('asset' === this.type) {
this.fields.push({key: 'role', label: this.$t('list.role'), sortable: !this.orderMode});
}
if ('liabilities' === this.type) {
this.fields.push({key: 'liability_type', label: this.$t('list.liability_type'), sortable: !this.orderMode});
this.fields.push({key: 'liability_direction', label: this.$t('list.liability_direction'), sortable: !this.orderMode});
this.fields.push({key: 'interest', label: this.$t('list.interest') + ' (' + this.$t('list.interest_period') + ')', sortable: !this.orderMode});
}
// add the rest
this.fields.push({key: 'acct_number', label: this.$t('list.iban'), sortable: !this.orderMode});
this.fields.push({key: 'current_balance', label: this.$t('list.currentBalance'), sortable: !this.orderMode});
if ('liabilities' === this.type) {
this.fields.push({key: 'amount_due', label: this.$t('firefly.left_in_debt'), sortable: !this.orderMode});
}
if ('asset' === this.type || 'liabilities' === this.type) {
this.fields.push({key: 'last_activity', label: this.$t('list.lastActivity'), sortable: !this.orderMode});
}
this.fields.push({key: 'menu', label: ' ', sortable: false});
},
getAccountList: function () {
// console.log('getAccountList()');
if (this.indexReady && !this.loading && !this.downloaded) {
// console.log('Index ready, not loading and not already downloaded. Reset.');
this.loading = true;
this.perPage = this.listPageSize ?? 51;
this.accounts = [];
this.allAccounts = [];
this.downloadAccountList(1);
}
if (this.indexReady && !this.loading && this.downloaded) {
// console.log('Index ready, not loading and not downloaded.');
this.loading = true;
this.filterAccountList();
}
},
downloadAccountList: function (page) {
// console.log('downloadAccountList(' + page + ')');
configureAxios().then(async (api) => {
api.get('./api/v1/accounts?type=' + this.type + '&page=' + page + '&key=' + this.cacheKey)
.then(response => {
let currentPage = parseInt(response.data.meta.pagination.current_page);
let totalPage = parseInt(response.data.meta.pagination.total_pages);
this.total = parseInt(response.data.meta.pagination.total);
this.parseAccounts(response.data.data);
if (currentPage < totalPage) {
let nextPage = currentPage + 1;
this.downloadAccountList(nextPage);
}
if (currentPage >= totalPage) {
// console.log('Looks like all downloaded.');
this.downloaded = true;
this.filterAccountList();
}
}
);
});
},
filterAccountListAndReturn: function (allAccounts) {
// console.log('filterAccountListAndReturn()');
let accounts = [];
for (let i in allAccounts) {
if (allAccounts.hasOwnProperty(i) && /^0$|^[1-9]\d*$/.test(i) && i <= 4294967294) {
// 1 = active only
// 2 = inactive only
// 3 = both
if (1 === this.activeFilter && false === allAccounts[i].active) {
// console.log('Skip account #' + this.allAccounts[i].id + ' because not active.');
continue;
}
if (2 === this.activeFilter && true === allAccounts[i].active) {
// console.log('Skip account #' + this.allAccounts[i].id + ' because active.');
continue;
}
// console.log('Include account #' + this.allAccounts[i].id + '.');
accounts.push(allAccounts[i]);
}
}
return accounts;
},
filterAccountList: function () {
// console.log('filterAccountList()');
this.accounts = [];
for (let i in this.allAccounts) {
if (this.allAccounts.hasOwnProperty(i) && /^0$|^[1-9]\d*$/.test(i) && i <= 4294967294) {
// 1 = active only
// 2 = inactive only
// 3 = both
if (1 === this.activeFilter && false === this.allAccounts[i].active) {
// console.log('Skip account #' + this.allAccounts[i].id + ' because not active.');
continue;
}
if (2 === this.activeFilter && true === this.allAccounts[i].active) {
// console.log('Skip account #' + this.allAccounts[i].id + ' because active.');
continue;
}
// console.log('Include account #' + this.allAccounts[i].id + '.');
this.accounts.push(this.allAccounts[i]);
}
}
this.total = this.accounts.length;
this.loading = false;
},
roleTranslate: function (role) {
if (null === role) {
return '';
}
return this.$t('firefly.account_role_' + role);
},
parsePages: function (data) {
this.total = parseInt(data.pagination.total);
// console.log('Total is now ' + this.total);
},
// parseAccountsAndReturn: function (data) {
// console.log('In parseAccountsAndReturn()');
// let allAccounts = [];
// for (let key in data) {
// if (data.hasOwnProperty(key) && /^0$|^[1-9]\d*$/.test(key) && key <= 4294967294) {
// let current = data[key];
// let acct = {};
// acct.id = parseInt(current.id);
// acct.order = current.attributes.order;
// acct.name = current.attributes.name;
// acct.active = current.attributes.active;
// acct.role = this.roleTranslate(current.attributes.account_role);
// acct.account_number = current.attributes.account_number;
// acct.current_balance = current.attributes.current_balance;
// acct.currency_code = current.attributes.currency_code;
//
// if ('liabilities' === this.type) {
// acct.liability_type = this.$t('firefly.account_type_' + current.attributes.liability_type);
// acct.liability_direction = this.$t('firefly.liability_direction_' + current.attributes.liability_direction + '_short');
// acct.interest = current.attributes.interest;
// acct.interest_period = this.$t('firefly.interest_calc_' + current.attributes.interest_period);
// acct.amount_due = current.attributes.current_debt;
// }
// acct.balance_diff = 'loading';
// acct.last_activity = 'loading';
//
// if (null !== current.attributes.iban) {
// acct.iban = current.attributes.iban.match(/.{1,4}/g).join(' ');
// }
// if (null === current.attributes.iban) {
// acct.iban = null;
// }
//
// allAccounts.push(acct);
// if ('asset' === this.type) {
// See reference nr. 9
// //this.getAccountBalanceDifference(this.allAccounts.length - 1, current);
// //this.getAccountLastActivity(this.allAccounts.length - 1, current);
// }
// }
// }
// return allAccounts;
// },
parseAccounts: function (data) {
// console.log('In parseAccounts()');
for (let key in data) {
if (data.hasOwnProperty(key) && /^0$|^[1-9]\d*$/.test(key) && key <= 4294967294) {
let current = data[key];
let acct = {};
acct.id = parseInt(current.id);
acct.order = current.attributes.order;
acct.name = current.attributes.name;
acct.active = current.attributes.active;
acct.role = this.roleTranslate(current.attributes.account_role);
// account number in 'acct_number'
acct.acct_number = '';
let iban = null;
let acctNr = null;
acct.acct_number = '';
if (null !== current.attributes.iban) {
iban = current.attributes.iban.match(/.{1,4}/g).join(' ');
}
if (null !== current.attributes.account_number) {
acctNr = current.attributes.account_number;
}
// only account nr
if (null === iban && null !== acctNr) {
acct.acct_number = acctNr;
}
// only iban
if (null !== iban && null === acctNr) {
acct.acct_number = iban;
}
// both:
if (null !== iban && null !== acctNr) {
acct.acct_number = iban + ' (' + acctNr + ')';
}
acct.current_balance = current.attributes.current_balance;
acct.currency_code = current.attributes.currency_code;
if ('liabilities' === this.type) {
acct.liability_type = this.$t('firefly.account_type_' + current.attributes.liability_type);
acct.liability_direction = this.$t('firefly.liability_direction_' + current.attributes.liability_direction + '_short');
acct.interest = current.attributes.interest;
acct.interest_period = this.$t('firefly.interest_calc_' + current.attributes.interest_period);
acct.amount_due = current.attributes.current_debt;
}
acct.balance_diff = 'loading';
acct.last_activity = 'loading';
this.allAccounts.push(acct);
if ('asset' === this.type) {
this.getAccountBalanceDifference(this.allAccounts.length - 1, current);
this.getAccountLastActivity(this.allAccounts.length - 1, current);
}
}
}
},
getAccountLastActivity: function (index, acct) {
// console.log('getAccountLastActivity(' + index + ')');
// get single transaction for account:
// /api/v1/accounts/1/transactions?limit=1
configureAxios().then(async (api) => {
api.get('./api/v1/accounts/' + acct.id + '/transactions?limit=1&key=' + this.cacheKey).then(response => {
if (0 === response.data.data.length) {
this.allAccounts[index].last_activity = 'none';
return;
}
let date = new Date(response.data.data[0].attributes.transactions[0].date);
this.allAccounts[index].last_activity = format(date, this.$t('config.month_and_day_fns'));
});
});
},
getAccountBalanceDifference: function (index, acct) {
// console.log('getAccountBalanceDifference(' + index + ')');
// get account on day 0
let promises = [];
// add meta data to promise context.
promises.push(Promise.resolve({
account: acct,
index: index,
}));
let startStr = format(this.start, 'y-MM-dd');
let endStr = format(this.end, 'y-MM-dd');
configureAxios().then(api => {
return api.get('./api/v1/accounts/' + acct.id + '?date=' + startStr + '&key=' + this.cacheKey);
});
//promises.push(axios.get('./api/v1/accounts/' + acct.id + '?date=' + startStr + '&key=' + this.cacheKey));
promises.push(configureAxios().then(api => {
return api.get('./api/v1/accounts/' + acct.id + '?date=' + startStr + '&key=' + this.cacheKey);
}));
promises.push(configureAxios().then(api => {
return api.get('./api/v1/accounts/' + acct.id + '?date=' + endStr + '&key=' + this.cacheKey);
}));
Promise.all(promises).then(responses => {
let index = responses[0].index;
let startBalance = parseFloat(responses[1].data.data.attributes.current_balance);
let endBalance = parseFloat(responses[2].data.data.attributes.current_balance);
this.allAccounts[index].balance_diff = endBalance - startBalance;
});
},
}
}
</script>

View File

@@ -1,90 +0,0 @@
<!--
- IndexOptions.vue
- 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/>.
-->
<template>
<div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="order_mode" id="order_mode" v-model="orderMode">
<label class="form-check-label" for="order_mode">
Enable order mode
</label>
</div>
<div class="form-check">
<input class="form-check-input" :disabled="orderMode" type="radio" value="1" v-model="activeFilter" id="active_filter_1">
<label class="form-check-label" for="active_filter_1">
Show active accounts
</label>
</div>
<div class="form-check">
<input class="form-check-input" :disabled="orderMode" type="radio" value="2" v-model="activeFilter" id="active_filter_2">
<label class="form-check-label" for="active_filter_2">
Show inactive accounts
</label>
</div>
<div class="form-check">
<input class="form-check-input" :disabled="orderMode" type="radio" value="3" v-model="activeFilter" id="active_filter_3">
<label class="form-check-label" for="active_filter_3">
Show both
</label>
</div>
</div>
</template>
<script>
export default {
name: "IndexOptions",
data() {
return {
type: 'invalid'
}
},
// watch orderMode, if its false then go to active in filter.
computed: {
orderMode: {
get() {
return this.$store.getters["accounts/index/orderMode"];
},
set(value) {
this.$store.commit('accounts/index/setOrderMode', value);
if(true===value) {
this.$store.commit('accounts/index/setActiveFilter', 1);
}
}
},
activeFilter: {
get() {
return this.$store.getters["accounts/index/activeFilter"];
},
set(value) {
this.$store.commit('accounts/index/setActiveFilter', parseInt(value));
}
},
},
created() {
let pathName = window.location.pathname;
let parts = pathName.split('/');
this.type = parts[parts.length - 1];
}
}
</script>

View File

@@ -1,70 +0,0 @@
<!--
- Interest.vue
- 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/>.
-->
<template>
<div class="form-group">
<div class="text-xs d-none d-lg-block d-xl-block">
{{ $t('form.interest') }}
</div>
<div class="input-group">
<input
v-model="interest"
:class="errors.length > 0 ? 'form-control is-invalid' : 'form-control'"
:placeholder="$t('form.interest')"
name="interest"
:disabled=disabled
type="number"
step="8"
/>
<div class="input-group-append">
<div class="input-group-text">%</div>
<button class="btn btn-outline-secondary" tabindex="-1" type="button"><span class="far fa-trash-alt"></span></button>
</div>
</div>
<span v-if="errors.length > 0">
<span v-for="error in errors" class="text-danger small">{{ error }}<br/></span>
</span>
</div>
</template>
<script>
export default {
name: "Interest",
props: {
value: {},
errors: {},
disabled: {
type: Boolean,
default: false
},
},
data() {
return {
interest: this.value
}
},
watch: {
interest: function (value) {
this.$emit('set-field', {field: 'interest', value: value});
},
}
}
</script>

View File

@@ -1,93 +0,0 @@
<!--
- InterestPeriod.vue
- 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/>.
-->
<template>
<div class="form-group">
<div class="text-xs d-none d-lg-block d-xl-block">
{{ $t('form.interest_period') }}
</div>
<div class="input-group" v-if="loading">
<span class="fas fa-spinner fa-spin"></span>
</div>
<div class="input-group" v-if="!loading">
<select
ref="interest_period"
v-model="interest_period"
:class="errors.length > 0 ? 'form-control is-invalid' : 'form-control'"
:title="$t('form.interest_period')"
autocomplete="off"
:disabled=disabled
name="interest_period"
>
<option v-for="period in this.periodList" :label="period.title" :value="period.slug">{{ period.title }}</option>
</select>
</div>
<span v-if="errors.length > 0">
<span v-for="error in errors" class="text-danger small">{{ error }}<br/></span>
</span>
</div>
</template>
<script>
export default {
name: "InterestPeriod",
props: {
value: {},
errors: {},
disabled: {
type: Boolean,
default: false
},
},
data() {
return {
periodList: [],
interest_period: this.value,
loading: true
}
},
methods: {
loadPeriods: function () {
//
axios.get('./api/v1/configuration/firefly.interest_periods')
.then(response => {
let content = response.data.data.value;
for (let i in content) {
if (content.hasOwnProperty(i)) {
let current = content[i];
this.periodList.push({slug: current, title: this.$t('firefly.interest_calc_' + current)})
}
}
this.loading = false;
}
);
}
},
watch: {
interest_period: function (value) {
this.$emit('set-field', {field: 'interest_period', value: value});
},
},
created() {
this.loadPeriods()
}
}
</script>

View File

@@ -1,68 +0,0 @@
<!--
- LiabilityAmount.vue
- 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/>.
-->
<template>
<div class="form-group">
<div class="text-xs d-none d-lg-block d-xl-block">
{{ $t('form.amount') }}
</div>
<div class="input-group">
<input
v-model="amount"
:class="errors.length > 0 ? 'form-control is-invalid' : 'form-control'"
:placeholder="$t('form.amount')"
name="liability_amount"
:disabled=disabled
type="number" step="any" min="0"
/>
<div class="input-group-append">
<button class="btn btn-outline-secondary" tabindex="-1" type="button"><span class="far fa-trash-alt"></span></button>
</div>
</div>
<span v-if="errors.length > 0">
<span v-for="error in errors" class="text-danger small">{{ error }}<br/></span>
</span>
</div>
</template>
<script>
export default {
name: "LiabilityAmount",
props: {
value: {},
errors: {},
disabled: {
type: Boolean,
default: false
},
},
data() {
return {
amount: this.value
}
},
watch: {
amount: function (value) {
this.$emit('set-field', {field: 'liability_amount', value: value});
},
}
}
</script>

View File

@@ -1,68 +0,0 @@
<!--
- LiabilityDate.vue
- 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/>.
-->
<template>
<div class="form-group">
<div class="text-xs d-none d-lg-block d-xl-block">
{{ $t('form.date') }}
</div>
<div class="input-group">
<input
v-model="date"
:class="errors.length > 0 ? 'form-control is-invalid' : 'form-control'"
:placeholder="$t('form.date')"
name="liability_date"
:disabled=disabled
type="date"
/>
<div class="input-group-append">
<button class="btn btn-outline-secondary" tabindex="-1" type="button"><span class="far fa-trash-alt"></span></button>
</div>
</div>
<span v-if="errors.length > 0">
<span v-for="error in errors" class="text-danger small">{{ error }}<br/></span>
</span>
</div>
</template>
<script>
export default {
name: "LiabilityDate",
props: {
value: {},
errors: {},
disabled: {
type: Boolean,
default: false
},
},
data() {
return {
date: this.value
}
},
watch: {
date: function (value) {
this.$emit('set-field', {field: 'liability_date', value: value});
},
}
}
</script>

View File

@@ -1,71 +0,0 @@
<!--
- LiabilityDirection.vue
- 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/>.
-->
<template>
<div class="form-group">
<div class="text-xs d-none d-lg-block d-xl-block">
{{ $t('form.liability_direction') }}
</div>
<div class="input-group">
<select
ref="liability_type"
v-model="liability_direction"
:class="errors.length > 0 ? 'form-control is-invalid' : 'form-control'"
:title="$t('form.liability_direction')"
autocomplete="off"
name="liability_direction"
:disabled=disabled
>
<option :label="$t('firefly.liability_direction_credit')" value="credit">{{ $t('firefly.liability_direction_credit') }}</option>
<option :label="$t('firefly.liability_direction_debit')" value="debit">{{ $t('firefly.liability_direction_debit') }}</option>
</select>
</div>
<span v-if="errors.length > 0">
<span v-for="error in errors" class="text-danger small">{{ error }}<br/></span>
</span>
</div>
</template>
<script>
export default {
name: "LiabilityDirection",
props: {
value: {},
errors: {},
disabled: {
type: Boolean,
default: false
},
},
data() {
return {
liability_direction: this.value
}
},
methods: {
},
watch: {
liability_direction: function (value) {
this.$emit('set-field', {field: 'liability_direction', value: value});
},
}
}
</script>

View File

@@ -1,93 +0,0 @@
<!--
- LiabilityType.vue
- 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/>.
-->
<template>
<div class="form-group">
<div class="text-xs d-none d-lg-block d-xl-block">
{{ $t('form.liability_type') }}
</div>
<div class="input-group" v-if="loading">
<span class="fas fa-spinner fa-spin"></span>
</div>
<div class="input-group" v-if="!loading">
<select
ref="liability_type"
v-model="liability_type"
:class="errors.length > 0 ? 'form-control is-invalid' : 'form-control'"
:title="$t('form.liability_type')"
autocomplete="off"
name="liability_type"
:disabled=disabled
>
<option v-for="type in this.typeList" :label="type.title" :value="type.slug">{{ type.title }}</option>
</select>
</div>
<span v-if="errors.length > 0">
<span v-for="error in errors" class="text-danger small">{{ error }}<br/></span>
</span>
</div>
</template>
<script>
export default {
name: "LiabilityType",
props: {
value: {},
errors: {},
disabled: {
type: Boolean,
default: false
},
},
data() {
return {
typeList: [],
liability_type: this.value,
loading: true
}
},
methods: {
loadRoles: function () {
//
axios.get('./api/v1/configuration/firefly.valid_liabilities')
.then(response => {
let content = response.data.data.value;
for (let i in content) {
if (content.hasOwnProperty(i)) {
let current = content[i];
this.typeList.push({slug: current, title: this.$t('firefly.account_type_' + current)})
}
}
this.loading = false;
}
);
}
},
watch: {
liability_type: function (value) {
this.$emit('set-field', {field: 'liability_type', value: value});
},
},
created() {
this.loadRoles()
}
}
</script>

View File

@@ -1,175 +0,0 @@
<!--
- Show.vue
- Copyright (c) 2020 james@firefly-iii.org
-
- This file is part of Firefly III (https://github.com/firefly-iii).
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div>
<div class="row">
<div class="col-lg-12 col-md-6 col-sm-12 col-xs-12">
<!-- Custom Tabs will be put here (see file history). -->
</div>
</div>
<TransactionListLarge
:entries="rawTransactions"
:isEmpty="isEmpty"
:page="currentPage"
ref="list"
:total="total"
:per-page="perPage"
:sort-desc="sortDesc"
v-on:jump-page="jumpToPage($event)"
v-on:refreshed-cache-key="refreshedKey"
/>
<div class="row">
<div class="col-lg-12 col-md-6 col-sm-12 col-xs-12">
<div class="card">
<div class="card-header">
<h3 class="card-title">
Blocks
</h3>
</div>
<div class="card-body">
Blocks
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import TransactionListLarge from "../transactions/TransactionListLarge";
import {mapGetters} from "vuex";
import format from "date-fns/format";
import {configureAxios} from "../../shared/forageStore";
export default {
name: "Show",
computed: {
...mapGetters('root', ['listPageSize', 'cacheKey']),
...mapGetters('dashboard/index', ['start', 'end',]),
'showReady': function () {
return null !== this.start && null !== this.end && null !== this.listPageSize && this.ready;
},
},
data() {
return {
accountId: 0,
rawTransactions: [],
ready: false,
loading: false,
total: 0,
sortDesc: false,
currentPage: 1,
perPage: 51,
locale: 'en-US',
api: null,
nameLoading: false,
isEmpty: false
}
},
created() {
this.ready = true;
let parts = window.location.pathname.split('/');
this.accountId = parseInt(parts[parts.length - 1]);
this.perPage = this.listPageSize ?? 51;
let params = new URLSearchParams(window.location.search);
this.currentPage = params.get('page') ? parseInt(params.get('page')) : 1;
this.getTransactions();
this.updatePageTitle();
},
components: {TransactionListLarge},
methods: {
updatePageTitle: function () {
if (this.showReady && !this.nameLoading) {
// update page title.
this.nameLoading = true;
configureAxios().then(async (api) => {
let url = './api/v1/accounts/' + this.accountId;
api.get(url)
.then(response => {
let start = new Intl.DateTimeFormat(this.locale, {year: 'numeric', month: 'long', day: 'numeric'}).format(this.start);
let end = new Intl.DateTimeFormat(this.locale, {year: 'numeric', month: 'long', day: 'numeric'}).format(this.end);
document.getElementById('page-subTitle').innerText = this.$t('firefly.journals_in_period_for_account_js', {
start: start,
end: end,
title: response.data.data.attributes.name
});
});
});
}
},
refreshedKey: function () {
this.loading = false;
this.getTransactions();
this.updatePageTitle();
},
getTransactions: function () {
// console.log('getTransactions()');
if (this.showReady && !this.loading) {
// console.log('Show ready, not loading, go for download');
this.loading = true;
this.rawTransactions = [];
configureAxios().then(async (api) => {
// console.log('Now getTransactions() x Start');
let startStr = format(this.start, 'y-MM-dd');
let endStr = format(this.end, 'y-MM-dd');
let url = './api/v1/accounts/' + this.accountId + '/transactions?page=' + this.currentPage + '&limit=' + this.perPage + '&start=' + startStr + '&end=' + endStr + '&cache=' + this.cacheKey;
api.get(url)
.then(response => {
// console.log('Now getTransactions() DONE!');
this.total = parseInt(response.data.meta.pagination.total);
if (0 === this.total) {
this.isEmpty = true;
}
// let transactions = response.data.data;
// console.log('Have downloaded ' + transactions.length + ' transactions');
// console.log(response.data);
this.rawTransactions = response.data.data;
this.loading = false;
}
);
});
}
},
jumpToPage: function (event) {
// console.log('noticed a change in account/show!');
this.currentPage = event.page;
this.getTransactions();
},
},
watch: {
start: function () {
this.getTransactions();
this.updatePageTitle();
},
end: function () {
this.getTransactions();
this.updatePageTitle();
},
}
}
</script>

View File

@@ -1,334 +0,0 @@
<!--
- Create.vue
- 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/>.
-->
<template>
<div>
<Alert :message="errorMessage" type="danger"/>
<Alert :message="successMessage" type="success"/>
<form @submit="submitForm" autocomplete="off">
<div class="col">
<!-- tabs -->
<ul class="nav nav-tabs ml-auto p-2" id="subscriptionTabs">
<li>
<li class="nav-item"><a class="nav-link active" href="#subscription" data-toggle="pill">
Subscription
</a>
</li>
<li>
<li class="nav-item"><a class="nav-link" href="#rule" data-toggle="pill">
Rule
</a>
</li>
</ul>
</div>
<div class="tab-content" id="subscriptionTabContent">
<div class="tab-pane show active" id="subscription" role="tabpanel" aria-labelledby="subscription-tab">
<div class="row">
<div class="col-xl-6 col-lg-6 col-md-12 col-sm-12 col-xs-12">
<div class="card card-primary">
<div class="card-header">
<h3 class="card-title">
{{ $t('firefly.mandatoryFields') }}
</h3>
</div>
<div class="card-body">
<GenericTextInput :disabled="submitting" v-model="name" field-name="name" :errors="errors.name" :title="$t('form.name')"
v-on:set-field="storeField($event)"/>
<GenericCurrency :disabled="submitting" v-model="currency_id" :errors="errors.currency_id" v-on:set-field="storeField($event)"/>
<GenericTextInput :disabled="submitting" field-type="number" field-step="any" v-model="amount_min"
field-name="amount_min" :errors="errors.amount_min" :title="$t('form.amount_min')" v-on:set-field="storeField($event)"/>
<GenericTextInput :disabled="submitting" field-type="number" field-step="any" v-model="amount_max"
field-name="amount_max" :errors="errors.amount_max" :title="$t('form.amount_max')" v-on:set-field="storeField($event)"/>
<GenericTextInput :disabled="submitting" field-type="date" v-model="date" field-name="date"
:errors="errors.date" :title="$t('form.startdate')" v-on:set-field="storeField($event)"/>
<GenericTextInput :disabled="submitting" field-type="date" v-model="end_date" field-name="end_date"
:errors="errors.end_date" :title="$t('form.end_date')" v-on:set-field="storeField($event)"/>
<GenericTextInput :disabled="submitting" field-type="date" v-model="extension_date" field-name="extension_date"
:errors="errors.extension_date" :title="$t('form.extension_date')" v-on:set-field="storeField($event)"/>
<RepeatFrequencyPeriod :disabled="submitting" v-model="repeat_freq" :errors="errors.repeat_freq"
v-on:set-field="storeField($event)"/>
</div>
</div>
</div>
<div class="col-xl-6 col-lg-6 col-md-12 col-sm-12 col-xs-12">
<div class="card">
<div class="card-header">
<h3 class="card-title">
{{ $t('firefly.optionalFields') }}
</h3>
</div>
<div class="card-body">
<GenericTextarea :disabled="submitting" field-name="notes" :title="$t('form.notes')" v-model="notes" :errors="errors.notes"
v-on:set-field="storeField($event)"/>
<GenericAttachments :disabled="submitting" :title="$t('form.attachments')" field-name="attachments" :errors="errors.attachments"
v-on:selected-attachments="selectedAttachments($event)"
v-on:selected-no-attachments="selectedNoAttachments($event)"
v-on:uploaded-attachments="uploadedAttachments($event)"
:upload-trigger="uploadTrigger"
:upload-object-type="uploadObjectType"
:upload-object-id="uploadObjectId"
/>
<GenericTextInput :disabled="submitting" v-model="skip" field-name="skip" :errors="errors.skip" :title="$t('form.skip')"
v-on:set-field="storeField($event)"/>
<GenericGroup :disabled="submitting" v-model="group_title" field-name="group_title" :errors="errors.group_title"
:title="$t('form.object_group')"
v-on:set-field="storeField($event)"/>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-xl-6 col-lg-6 col-md-12 col-sm-12 col-xs-12 offset-xl-6 offset-lg-6">
<div class="card">
<div class="card-body">
<div class="row">
<div class="col-lg-6 offset-lg-6">
<button :disabled=submitting type="button" @click="submitForm" class="btn btn-success btn-block">{{
$t('firefly.store_new_bill')
}}
</button>
<div class="form-check">
<input id="createAnother" v-model="createAnother" class="form-check-input" type="checkbox">
<label class="form-check-label" for="createAnother">
<span class="small">{{ $t('firefly.create_another') }}</span>
</label>
</div>
<div class="form-check">
<input id="resetFormAfter" v-model="resetFormAfter" :disabled="!createAnother" class="form-check-input" type="checkbox">
<label class="form-check-label" for="resetFormAfter">
<span class="small">{{ $t('firefly.reset_after') }}</span>
</label>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="tab-pane show" id="rule" role="tabpanel" aria-labelledby="rule-tab">
<div class="row">
<div class="col-xl-6 col-lg-6 col-md-12 col-sm-12 col-xs-12">
<div class="card card-primary">
<div class="card-header">
<h3 class="card-title">
Title
</h3>
</div>
<div class="card-body">
<p>
In the future here you can set rule info for the new subscription.
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</form>
</div>
</template>
<script>
import RepeatFrequencyPeriod from "./RepeatFrequencyPeriod";
import Alert from '../partials/Alert';
import GenericTextInput from "../form/GenericTextInput";
import GenericCurrency from "../form/GenericCurrency";
import GenericTextarea from "../form/GenericTextarea";
import GenericAttachments from "../form/GenericAttachments";
import GenericGroup from "../form/GenericGroup";
import format from "date-fns/format";
const lodashClonedeep = require('lodash.clonedeep');
export default {
name: "Create",
components: {RepeatFrequencyPeriod, GenericAttachments, GenericGroup, GenericTextarea, Alert, GenericTextInput, GenericCurrency},
data() {
return {
submitting: false,
successMessage: '',
errorMessage: '',
createAnother: false,
resetFormAfter: false,
returnedId: 0,
returnedTitle: '',
// fields
name: '',
currency_id: null,
amount_min: '',
amount_max: '',
date: '',
end_date: '',
extension_date: '',
repeat_freq: 'monthly',
// optional fields
notes: '',
skip: '0',
group_title: '',
// has attachments to upload?
hasAttachments: false,
uploadTrigger: false,
uploadObjectId: 0,
uploadObjectType: 'Bill',
// optional fields:
location: {},
// errors
errors: {
currency_id: [],
repeat_freq: [],
group_title: [],
},
defaultErrors: {
name: [],
group_title: [],
currency_id: [],
amount_min: [],
amount_max: [],
date: [],
end_date: [],
extension_date: [],
repeat_freq: [],
}
}
},
created() {
this.date = format(new Date, 'yyyy-MM-dd');
},
methods: {
storeField: function (payload) {
// console.log(payload);
if ('location' === payload.field) {
if (true === payload.value.hasMarker) {
this.location = payload.value;
return;
}
this.location = {};
return;
}
this[payload.field] = payload.value;
},
selectedAttachments: function (e) {
this.hasAttachments = true;
},
selectedNoAttachments: function (e) {
this.hasAttachments = false;
},
submitForm: function (e) {
e.preventDefault();
this.submitting = true;
let submission = this.getSubmission();
// console.log('Will submit:');
// console.log(submission);
let url = './api/v1/bills';
axios.post(url, submission)
.then(response => {
this.errors = lodashClonedeep(this.defaultErrors);
this.returnedId = parseInt(response.data.data.id);
this.returnedTitle = response.data.data.attributes.name;
if (this.hasAttachments) {
// upload attachments. Do a callback to a finish up method.
//alert('must now upload!');
this.uploadObjectId = this.returnedId;
this.uploadTrigger = true;
}
if (!this.hasAttachments) {
this.finishSubmission();
}
})
.catch(error => {
this.submitting = false;
this.parseErrors(error.response.data);
// display errors!
});
},
uploadedAttachments: function (e) {
this.finishSubmission();
},
finishSubmission: function () {
this.successMessage = this.$t('firefly.stored_new_bill_js', {ID: this.returnedId, name: this.returnedTitle});
// stay here is false?
if (false === this.createAnother) {
window.location.href = (window.previousURL ?? '/') + '?bill_id=' + this.returnedId + '&message=created';
return;
}
this.submitting = false;
if (this.resetFormAfter) {
// console.log('reset!');
this.name = '';
}
},
parseErrors: function (errors) {
this.errors = lodashClonedeep(this.defaultErrors);
// console.log(errors);
for (let i in errors.errors) {
if (errors.errors.hasOwnProperty(i)) {
this.errors[i] = errors.errors[i];
}
}
},
getSubmission: function () {
let submission = {
name: this.name,
currency_id: this.currency_id,
amount_min: this.amount_min,
amount_max: this.amount_max,
date: this.date,
repeat_freq: this.repeat_freq,
skip: this.skip,
active: true,
object_group_title: this.group_title
};
if (Object.keys(this.location).length >= 3) {
submission.longitude = this.location.lng;
submission.latitude = this.location.lat;
submission.zoom_level = this.location.zoomLevel;
}
if ('' !== this.end_date) {
submission.end_date = this.end_date;
}
if ('' !== this.extension_date) {
submission.extension_date = this.extension_date;
}
if ('' !== this.notes) {
submission.notes = this.notes;
}
return submission;
}
}
}
</script>

View File

@@ -1,360 +0,0 @@
<!--
- Index.vue
- 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/>.
-->
<template>
<div>
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<a href="./subscriptions/create" class="btn btn-sm mb-2 float-right btn-success"><span class="fas fa-plus"></span> {{
$t('firefly.create_new_bill')
}}</a>
<button @click="newCacheKey" class="btn btn-sm mb-2 mr-2 float-right btn-info"><span class="fas fa-sync"></span></button>
</div>
</div>
<div class="row" v-for="group in sortedGroups">
<div v-if="group[1].bills.length > 0" class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<div class="card">
<div class="card-header">
<h3 class="card-title">
{{ group[1].title }}
</h3>
</div>
<div class="card-body p-0">
<b-table id="my-table" striped hover responsive="md" primary-key="id" :no-local-sorting="false"
:items="group[1].bills"
sort-icon-left
:fields="fields"
:busy.sync="loading"
>
<template #cell(name)="data">
<a :href="'./bills/show/' + data.item.id">{{ data.item.name }}</a>
<br/>
<small v-if="true === data.item.active && 0 === data.item.skip">{{ $t('firefly.bill_repeats_' + data.item.repeat_freq) }}</small>
<small v-if="true === data.item.active && 1 === data.item.skip">{{ $t('firefly.bill_repeats_' + data.item.repeat_freq + '_other') }}</small>
<small v-if="true === data.item.active && data.item.skip > 1">{{
$t('firefly.bill_repeats_' + data.item.repeat_freq + '_skip', {skip: data.item.skip + 1})
}}</small>
<small v-if="false === data.item.active">{{ $t('firefly.inactive') }}</small>
<!-- (rules, recurring) -->
</template>
<template #cell(expected_info)="data">
<span v-if="true === data.item.active && data.item.paid_dates.length > 0 && data.item.pay_dates.length > 0">
{{
new Intl.DateTimeFormat(locale, {
month: 'long',
year: 'numeric',
day: 'numeric'
}).format(new Date(data.item.next_expected_match.substring(0, 10)))
}}
<br>
</span>
<!--
not paid, not expected and active
-->
<span v-if="0 === data.item.paid_dates.length && 0 === data.item.pay_dates.length && true === data.item.active">
{{ $t('firefly.not_expected_period') }}
</span>
<!--
not paid but expected
-->
<span :title="new Intl.DateTimeFormat(locale, {
month: 'long',
year: 'numeric',
day: 'numeric'
}).format(new Date(data.item.pay_dates[0].substring(0,10)))"
class="text-danger" v-if="0 === data.item.paid_dates.length && data.item.pay_dates.length > 0 && true === data.item.active">
{{ $t('firefly.bill_expected_date_js', {date: data.item.next_expected_match_diff}) }}
</span>
<!--
bill is not active
-->
<span v-if="false === data.item.active">
~
</span>
</template>
<template #cell(start_date)="data">
{{
new Intl.DateTimeFormat(locale, {
year: 'numeric',
month: 'long',
day: 'numeric'
}).format(new Date(data.item.date.substring(0, 10)))
}}
</template>
<template #cell(end_date)="data">
<span v-if="null !== data.item.end_date">
{{
new Intl.DateTimeFormat(locale, {
year: 'numeric',
month: 'long',
day: 'numeric'
}).format(new Date(data.item.end_date.substring(0, 10)))
}}
</span>
<span v-if="null === data.item.end_date">{{ $t('firefly.forever') }}</span>
<span v-if="null !== data.item.extension_date"><br/>
<small>
{{
$t('firefly.extension_date_is', {
date: new Intl.DateTimeFormat(locale, {
year: 'numeric',
month: 'long',
day: 'numeric'
}).format(new Date(data.item.extension_date.substring(0, 10)))
})
}}
</small>
</span>
</template>
<template #cell(amount)="data">
~ <span class="text-info">{{
Intl.NumberFormat(locale, {style: 'currency', currency: data.item.currency_code}).format((data.item.amount_min + data.item.amount_max) / 2)
}}
</span>
</template>
<template #cell(payment_info)="data">
<!--
paid_dates >= 0 (bill is paid X times).
Don't care about pay_dates.
-->
<span v-if="data.item.paid_dates.length > 0 && true === data.item.active">
<span v-for="currentPaid in data.item.paid_dates">
<a :href="'./transactions/show/' + currentPaid.transaction_group_id">
{{
new Intl.DateTimeFormat(locale, {
year: 'numeric',
month: 'long',
day: 'numeric'
}).format(new Date(currentPaid.date.substring(0, 10)))
}}
</a>
<br/>
</span>
</span>
<!--
bill is not active
-->
<span v-if="false === data.item.active">
~
</span>
</template>
<template #cell(menu)="data">
<div class="btn-group btn-group-sm">
<div class="dropdown">
<button class="btn btn-light btn-sm dropdown-toggle" type="button" :id="'dropdownMenuButton' + data.item.id" data-toggle="dropdown"
aria-haspopup="true" aria-expanded="false">
{{ $t('firefly.actions') }}
</button>
<div class="dropdown-menu" :aria-labelledby="'dropdownMenuButton' + data.item.id">
<a class="dropdown-item" :href="'./subscriptions/edit/' + data.item.id"><span class="fa fas fa-pencil-alt"></span> {{
$t('firefly.edit')
}}</a>
<a class="dropdown-item" :href="'./subscriptions/delete/' + data.item.id"><span class="fa far fa-trash"></span> {{
$t('firefly.delete')
}}</a>
</div>
</div>
</div>
</template>
</b-table>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<a href="./subscriptions/create" class="btn btn-sm mt-2 float-right btn-success"><span class="fas fa-plus"></span> {{
$t('firefly.create_new_bill')
}}</a>
<button @click="newCacheKey" class="btn btn-sm mt-2 mr-2 float-right btn-info"><span class="fas fa-sync"></span></button>
</div>
</div>
</div>
</template>
<script>
import {mapGetters, mapMutations} from "vuex";
import {configureAxios} from "../../shared/forageStore";
import format from "date-fns/format";
export default {
name: "Index",
data() {
return {
groups: {},
downloaded: false,
loading: false,
locale: 'en-US',
sortedGroups: [],
fields: [],
fnsLocale: null,
ready: false
}
},
watch: {
start: function () {
this.downloadBills(1);
},
end: function () {
this.downloadBills(1);
},
},
computed: {
...mapGetters('root', ['listPageSize', 'cacheKey']),
...mapGetters('dashboard/index', ['start', 'end',]),
'indexReady': function () {
return null !== this.start && null !== this.end && null !== this.listPageSize && this.ready;
},
},
created() {
this.locale = localStorage.locale ?? 'en-US';
this.updateFieldList();
this.ready = true;
},
methods: {
...mapMutations('root', ['refreshCacheKey',]),
formatDate: function (date, frm) {
return format(date, frm, {locale: {code: this.locale}});
},
updateFieldList: function () {
this.fields = [];
this.fields.push({key: 'name', label: this.$t('list.name')});
this.fields.push({key: 'expected_info', label: this.$t('list.expected_info')});
this.fields.push({key: 'start_date', label: this.$t('list.start_date')});
this.fields.push({key: 'end_date', label: this.$t('list.end_date')});
this.fields.push({key: 'amount', label: this.$t('list.amount')});
this.fields.push({key: 'payment_info', label: this.$t('list.payment_info')});
this.fields.push({key: 'menu', label: ' ', sortable: false});
},
newCacheKey: function () {
this.refreshCacheKey();
this.downloaded = false;
this.accounts = [];
this.downloadBills(1);
},
resetGroups: function () {
this.groups = {};
this.groups[0] =
{
id: 0,
title: this.$t('firefly.default_group_title_name'),
order: 1,
bills: []
};
},
downloadBills: function (page) {
// console.log('downloadBills');
// console.log(this.indexReady);
// console.log(this.loading);
// console.log(this.downloaded);
this.resetGroups();
// console.log('getAccountList()');
if (this.indexReady && !this.loading && !this.downloaded) {
this.loading = true;
configureAxios().then(async (api) => {
// get date from session.
let startStr = format(this.start, 'y-MM-dd');
let endStr = format(this.end, 'y-MM-dd');
api.get('./api/v1/bills?page=' + page + '&key=' + this.cacheKey + '&start='+startStr+'&end=' + endStr)
.then(response => {
// pages
let currentPage = parseInt(response.data.meta.pagination.current_page);
let totalPage = parseInt(response.data.meta.pagination.total_pages);
this.parseBills(response.data.data);
if (currentPage < totalPage) {
let nextPage = currentPage + 1;
this.downloadBills(nextPage);
}
if (currentPage >= totalPage) {
this.downloaded = true;
}
this.sortGroups();
}
);
});
}
},
sortGroups: function () {
const sortable = Object.entries(this.groups);
//console.log('sortable');
//console.log(sortable);
sortable.sort(function (a, b) {
return a.order - b.order;
});
this.sortedGroups = sortable;
this.loading = false;
//console.log(this.sortedGroups);
},
parseBills: function (data) {
for (let key in data) {
if (data.hasOwnProperty(key) && /^0$|^[1-9]\d*$/.test(key) && key <= 4294967294) {
let current = data[key];
let bill = {};
// create group of necessary.
let groupId = null === current.attributes.object_group_id ? 0 : parseInt(current.attributes.object_group_id);
if (0 !== groupId && !(groupId in this.groups)) {
this.groups[groupId] = {
id: groupId,
title: current.attributes.object_group_title,
order: parseInt(current.attributes.object_group_order),
bills: []
}
}
bill.id = parseInt(current.id);
bill.order = parseInt(current.attributes.order);
bill.name = current.attributes.name;
bill.repeat_freq = current.attributes.repeat_freq;
bill.skip = current.attributes.skip;
bill.active = current.attributes.active;
bill.date = current.attributes.date;
bill.end_date = current.attributes.end_date;
bill.extension_date = current.attributes.extension_date;
bill.amount_max = parseFloat(current.attributes.amount_max);
bill.amount_min = parseFloat(current.attributes.amount_min);
bill.currency_code = current.attributes.currency_code;
bill.currency_id = parseInt(current.attributes.currency_id);
bill.currency_decimal_places = parseInt(current.attributes.currency_decimal_places);
bill.currency_symbol = current.attributes.currency_symbol;
bill.next_expected_match = current.attributes.next_expected_match;
bill.next_expected_match_diff = current.attributes.next_expected_match_diff;
bill.notes = current.attributes.notes;
bill.paid_dates = current.attributes.paid_dates;
bill.pay_dates = current.attributes.pay_dates;
this.groups[groupId].bills.push(bill);
}
}
}
}
}
</script>

View File

@@ -1,101 +0,0 @@
<!--
- InterestPeriod.vue
- 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/>.
-->
<template>
<div class="form-group">
<div class="text-xs d-none d-lg-block d-xl-block">
{{ $t('form.repeat_freq') }}
</div>
<div class="input-group" v-if="loading">
<span class="fas fa-spinner fa-spin"></span>
</div>
<div class="input-group" v-if="!loading">
<select
ref="repeat_freq"
v-model="repeat_freq"
:class="errors.length > 0 ? 'form-control is-invalid' : 'form-control'"
:title="$t('form.repeat_freq')"
autocomplete="off"
:disabled=disabled
name="repeat_freq"
>
<option v-for="period in this.periodList" :label="period.title" :value="period.slug">{{ period.title }}</option>
</select>
</div>
<span v-if="errors.length > 0">
<span v-for="error in errors" class="text-danger small">{{ error }}<br/></span>
</span>
</div>
</template>
<script>
import {configureAxios} from "../../shared/forageStore";
import {mapGetters, mapMutations} from "vuex";
export default {
name: "RepeatFrequencyPeriod",
props: {
value: {},
errors: {},
disabled: {
type: Boolean,
default: false
},
},
computed: {
...mapGetters('root', [ 'cacheKey']),
},
data() {
return {
periodList: [],
repeat_freq: this.value,
loading: true
}
},
methods: {
...mapMutations('root', ['refreshCacheKey',]),
loadPeriods: function () {
configureAxios().then(async (api) => {
api.get('./api/v1/configuration/firefly.bill_periods?key=' + this.cacheKey)
.then(response => {
let content = response.data.data.value;
for (let i in content) {
if (content.hasOwnProperty(i)) {
let current = content[i];
this.periodList.push({slug: current, title: this.$t('firefly.repeat_freq_' + current)})
}
}
this.loading = false;
}
);
});
}
},
watch: {
repeat_freq: function (value) {
this.$emit('set-field', {field: 'repeat_freq', value: value});
},
},
created() {
this.loadPeriods()
}
}
</script>

View File

@@ -1,111 +0,0 @@
<!--
- Index.vue
- 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/>.
-->
<template>
<div>
<p>
<span class="d-block">(all)</span>
<span class="d-none d-xl-block">xl</span>
<span class="d-none d-lg-block d-xl-none">lg</span>
<span class="d-none d-md-block d-lg-none">md</span>
<span class="d-none d-sm-block d-md-none">sm</span>
<span class="d-block d-sm-none">xs</span>
</p>
<div class="row">
<div class="col-xl-2 col-lg-4 col-md-4 col-sm-4 col-6">
<div class="card card-primary">
<div class="card-header">
<h3 class="card-title">Budgets</h3>
</div>
<div class="card-body">
Budget X<br>
Budget Y<br>
Budget X<br>
Budget Y<br>
Budget X<br>
Budget Y<br>
Budget X<br>
Budget Y<br>
</div>
</div>
</div>
<div class="col-xl-10 col-lg-8 col-md-8 col-sm-8 col-6">
<div class="container-fluid" style="overflow:scroll;">
<div class="d-flex flex-row flex-nowrap">
<div class="card card-body-budget" v-for="n in 5">
<div class="card-header">
<h3 class="card-title">Maand yXz</h3>
</div>
<div class="card-body">
Some text<br>
Some text<br>
Some text<br>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "Index"
}
</script>
<style scoped>
.card-body-budget {
min-width: 300px;
margin-right: 5px;
}
.holder-titles {
display: flex;
flex-direction: column-reverse;
}
.title-block {
border: 1px red solid;
}
.holder-blocks {
display: flex;
flex-direction: column-reverse;
}
.budget-block {
border: 1px blue solid;
}
.budget-block-unused {
border: 1px green solid;
}
.budget-block-unset {
border: 1px purple solid;
}
</style>

View File

@@ -1,187 +0,0 @@
<!--
- DataConverter.vue
- Copyright (c) 2020 james@firefly-iii.org
-
- This file is part of Firefly III (https://github.com/firefly-iii).
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<script>
export default {
name: "DataConverter",
data() {
return {
dataSet: null,
newDataSet: null,
locale: localStorage.local,
}
},
methods: {
convertChart(dataSet) {
this.dataSet = dataSet;
this.newDataSet = {
//count: 0,
labels: [],
datasets: []
}
this.getLabels();
this.getDataSets();
//this.newDataSet.count = this.newDataSet.datasets.length;
return this.newDataSet;
},
colorizeBarData(dataSet) {
this.dataSet = dataSet;
this.newDataSet = {
//count: 0,
labels: [],
datasets: []
};
// colors
let colourSet = [
[53, 124, 165],
[0, 141, 76], // green
[219, 139, 11],
[202, 25, 90], // paars rood-ish #CA195A
[85, 82, 153],
[66, 133, 244],
[219, 68, 55], // red #DB4437
[244, 180, 0],
[15, 157, 88],
[171, 71, 188],
[0, 172, 193],
[255, 112, 67],
[158, 157, 36],
[92, 107, 192],
[240, 98, 146],
[0, 121, 107],
[194, 24, 91]
];
let fillColors = [];
//let strokePointHighColors = [];
//for (let i = 0; i < colourSet.length; i++) {
for (let value of colourSet) {
fillColors.push("rgba(" + value[0] + ", " + value[1] + ", " + value[2] + ", 0.5)");
//strokePointHighColors.push("rgba(" + colourSet[i][0] + ", " + colourSet[i][1] + ", " + colourSet[i][2] + ", 0.9)");
}
this.newDataSet.labels = this.dataSet.labels;
//this.newDataSet.count = this.dataSet.count;
for (let setKey in this.dataSet.datasets) {
if (this.dataSet.datasets.hasOwnProperty(setKey)) {
var dataset = this.dataSet.datasets[setKey];
dataset.fill = false;
dataset.backgroundColor = dataset.borderColor = fillColors[setKey];
this.newDataSet.datasets.push(dataset);
}
}
return this.newDataSet;
},
colorizeLineData(dataSet) {
this.dataSet = dataSet;
this.newDataSet = {
//count: 0,
labels: [],
datasets: []
};
// colors
let colourSet = [
[53, 124, 165],
[0, 141, 76], // green
[219, 139, 11],
[202, 25, 90], // paars rood-ish #CA195A
[85, 82, 153],
[66, 133, 244],
[219, 68, 55], // red #DB4437
[244, 180, 0],
[15, 157, 88],
[171, 71, 188],
[0, 172, 193],
[255, 112, 67],
[158, 157, 36],
[92, 107, 192],
[240, 98, 146],
[0, 121, 107],
[194, 24, 91]
];
let fillColors = [];
//let strokePointHighColors = [];
//for (let i = 0; i < colourSet.length; i++) {
for (let value of colourSet) {
fillColors.push("rgba(" + value[0] + ", " + value[1] + ", " + value[2] + ", 0.5)");
//strokePointHighColors.push("rgba(" + colourSet[i][0] + ", " + colourSet[i][1] + ", " + colourSet[i][2] + ", 0.9)");
}
this.newDataSet.labels = this.dataSet.labels;
//this.newDataSet.count = this.dataSet.count;
for (let setKey in this.dataSet.datasets) {
if (this.dataSet.datasets.hasOwnProperty(setKey)) {
let dataset = this.dataSet.datasets[setKey];
dataset.fill = false;
dataset.backgroundColor = dataset.borderColor = fillColors[setKey];
this.newDataSet.datasets.push(dataset);
}
}
return this.newDataSet;
},
convertLabelsToDate(dataSet) {
for (let labelKey in dataSet.labels) {
if (dataSet.labels.hasOwnProperty(labelKey)) {
const unixTimeZero = Date.parse(dataSet.labels[labelKey]);
dataSet.labels[labelKey] = new Intl.DateTimeFormat(this.locale).format(unixTimeZero);
}
}
return dataSet;
},
getLabels() {
let firstSet = this.dataSet[0];
if (typeof firstSet !== 'undefined') {
for (const entryLabel in firstSet.entries) {
if (firstSet.entries.hasOwnProperty(entryLabel)) {
this.newDataSet.labels.push(entryLabel);
}
}
}
},
getDataSets() {
for (const setKey in this.dataSet) {
if (this.dataSet.hasOwnProperty(setKey)) {
let newSet = {};
let oldSet = this.dataSet[setKey];
if (typeof oldSet !== 'undefined') {
newSet.label = oldSet.label;
newSet.type = oldSet.type;
newSet.currency_symbol = oldSet.currency_symbol;
newSet.currency_code = oldSet.currency_code;
//newSet.yAxisID = oldSet.yAxisID;
newSet.data = [];
for (const entryLabel in oldSet.entries) {
if (oldSet.entries.hasOwnProperty(entryLabel)) {
newSet.data.push(oldSet.entries[entryLabel]);
}
}
this.newDataSet.datasets.push(newSet);
}
}
}
}
}
}
</script>

View File

@@ -1,113 +0,0 @@
<!--
- DefaultLineOptions.vue
- Copyright (c) 2020 james@firefly-iii.org
-
- This file is part of Firefly III (https://github.com/firefly-iii).
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
</template>
<script>
import FormatLabel from "../charts/FormatLabel";
export default {
name: "DefaultBarOptions",
data() {
return {}
},
methods: {
getDefaultOptions() {
return {
type: 'bar',
layout: {
padding: {
left: 50,
right: 50,
top: 0,
bottom: 0
},
},
stacked: true,
elements: {
line: {
cubicInterpolationMode: 'monotone'
}
},
legend: {
display: false,
},
animation: {
duration: 0,
},
responsive: true,
maintainAspectRatio: false,
scales: {
xAxes: [
{
stacked: true,
gridLines: {
display: false
},
ticks: {
// break ticks when too long.
callback: function (value, index, values) {
return FormatLabel.methods.formatLabel(value, 20);
//return value;
}
}
}
],
yAxes: [{
stacked: false,
display: true,
drawOnChartArea: false,
offset: true,
beginAtZero: true,
ticks: {
callback: function (tickValue) {
"use strict";
let currencyCode = this.chart.data.datasets[0] ? this.chart.data.datasets[0].currency_code : 'EUR';
return new Intl.NumberFormat(localStorage.locale, {style: 'currency', currency: currencyCode}).format(tickValue);
},
}
}]
},
tooltips: {
mode: 'label',
callbacks: {
label: function (tooltipItem, data) {
"use strict";
let currencyCode = data.datasets[tooltipItem.datasetIndex] ? data.datasets[tooltipItem.datasetIndex].currency_code : 'EUR';
let nrString = new Intl.NumberFormat(localStorage.locale, {
style: 'currency',
currency: currencyCode
}).format(tooltipItem.yLabel);
return data.datasets[tooltipItem.datasetIndex].label + ': ' + nrString;
}
}
}
};
}
}
}
</script>

View File

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

View File

@@ -1,30 +0,0 @@
<!--
- DefaultPieOptions.vue
- Copyright (c) 2020 james@firefly-iii.org
-
- This file is part of Firefly III (https://github.com/firefly-iii).
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
</template>
<script>
export default {
name: "DefaultPieOptions"
}
</script>

View File

@@ -1,73 +0,0 @@
<!--
- FormatLabel.vue
- Copyright (c) 2020 james@firefly-iii.org
-
- This file is part of Firefly III (https://github.com/firefly-iii).
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<script>
export default {
name: "FormatLabel",
methods: {
/**
* Takes a string phrase and breaks it into separate phrases no bigger than 'maxwidth', breaks are made at complete words.
* https://stackoverflow.com/questions/21409717/chart-js-and-long-labels
*
* @param str
* @param maxwidth
* @returns {Array}
*/
formatLabel(str, maxwidth) {
var sections = [];
str = String(str);
var words = str.split(" ");
var temp = "";
words.forEach(function (item, index) {
if (temp.length > 0) {
var concat = temp + ' ' + item;
if (concat.length > maxwidth) {
sections.push(temp);
temp = "";
} else {
if (index === (words.length - 1)) {
sections.push(concat);
return;
} else {
temp = concat;
return;
}
}
}
if (index === (words.length - 1)) {
sections.push(item);
return;
}
if (item.length < maxwidth) {
temp = item;
} else {
sections.push(item);
}
});
return sections;
},
}
}
</script>

View File

@@ -1,115 +0,0 @@
<!--
- BudgetRow.vue
- Copyright (c) 2020 james@firefly-iii.org
-
- This file is part of Firefly III (https://github.com/firefly-iii).
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<tr>
<td style="width:25%;">
<a :href="'./budgets/show/' + budgetLimit.budget_id">{{ budgetLimit.budget_name }}</a>
</td>
<td style="vertical-align: middle">
<div class="progress progress active">
<div :aria-valuenow="budgetLimit.pctGreen" :style="'width: '+ budgetLimit.pctGreen + '%;'"
aria-valuemax="100" aria-valuemin="0" class="progress-bar bg-success" role="progressbar">
<span v-if="budgetLimit.pctGreen > 35">
{{ $t('firefly.spent_x_of_y', {amount: Intl.NumberFormat(locale, {style: 'currency', currency: budgetLimit.currency_code}).format(budgetLimit.spent), total: Intl.NumberFormat(locale, {style: 'currency', currency: budgetLimit.currency_code}).format(budgetLimit.amount)}) }}
</span>
</div>
<div :aria-valuenow="budgetLimit.pctOrange" :style="'width: '+ budgetLimit.pctOrange + '%;'"
aria-valuemax="100" aria-valuemin="0" class="progress-bar bg-warning" role="progressbar">
<span v-if="budgetLimit.pctRed <= 50 && budgetLimit.pctOrange > 35">
{{ $t('firefly.spent_x_of_y', {amount: Intl.NumberFormat(locale, {style: 'currency', currency: budgetLimit.currency_code}).format(budgetLimit.spent), total: Intl.NumberFormat(locale, {style: 'currency', currency: budgetLimit.currency_code}).format(budgetLimit.amount)}) }}
</span>
</div>
<div :aria-valuenow="budgetLimit.pctRed" :style="'width: '+ budgetLimit.pctRed + '%;'"
aria-valuemax="100" aria-valuemin="0" class="progress-bar bg-danger" role="progressbar">
<span v-if="budgetLimit.pctOrange <= 50 && budgetLimit.pctRed > 35" class="text-white">
{{ $t('firefly.spent_x_of_y', {amount: Intl.NumberFormat(locale, {style: 'currency', currency: budgetLimit.currency_code}).format(budgetLimit.spent), total: Intl.NumberFormat(locale, {style: 'currency', currency: budgetLimit.currency_code}).format(budgetLimit.amount)}) }}
</span>
</div>
<!-- amount if bar is very small -->
<span v-if="budgetLimit.pctGreen <= 35 && 0 === budgetLimit.pctOrange && 0 === budgetLimit.pctRed && 0 !== budgetLimit.pctGreen" style="line-height: 16px;">
&nbsp; {{ $t('firefly.spent_x_of_y', {amount: Intl.NumberFormat(locale, {style: 'currency', currency: budgetLimit.currency_code}).format(budgetLimit.spent), total: Intl.NumberFormat(locale, {style: 'currency', currency: budgetLimit.currency_code}).format(budgetLimit.amount)}) }}
</span>
</div>
<small class="d-none d-lg-block">
{{ new Intl.DateTimeFormat(locale, {year: 'numeric', month: 'long', day: 'numeric'}).format(budgetLimit.start) }}
&rarr;
{{ new Intl.DateTimeFormat(locale, {year: 'numeric', month: 'long', day: 'numeric'}).format(budgetLimit.end) }}
</small>
</td>
<td class="align-middle d-none d-lg-table-cell" style="width:10%;">
<span v-if="parseFloat(budgetLimit.amount) + parseFloat(budgetLimit.spent) > 0" class="text-success">
{{
Intl.NumberFormat(locale, {
style: 'currency',
currency: budgetLimit.currency_code
}).format(parseFloat(budgetLimit.amount) + parseFloat(budgetLimit.spent))
}}
</span>
<span v-if="0.0 === parseFloat(budgetLimit.amount) + parseFloat(budgetLimit.spent)" class="text-muted">
{{ Intl.NumberFormat(locale, {style: 'currency', currency: budgetLimit.currency_code}).format(0) }}
</span>
<span v-if="parseFloat(budgetLimit.amount) + parseFloat(budgetLimit.spent) < 0" class="text-danger">
{{
Intl.NumberFormat(locale, {
style: 'currency',
currency: budgetLimit.currency_code
}).format(parseFloat(budgetLimit.amount) + parseFloat(budgetLimit.spent))
}}
</span>
</td>
</tr>
</template>
<script>
export default {
name: "BudgetLimitRow",
created() {
this.locale = localStorage.locale ?? 'en-US';
},
data() {
return {
locale: 'en-US',
}
},
props: {
budgetLimit: {
type: Object,
default: function () {
return {};
}
},
budget: {
type: Object,
default: function () {
return {};
}
}
}
}
</script>

View File

@@ -1,62 +0,0 @@
<!--
- BudgetListGroup.vue
- Copyright (c) 2020 james@firefly-iii.org
-
- This file is part of Firefly III (https://github.com/firefly-iii).
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div class="card">
<div class="card-header">
<h3 class="card-title">{{ title }}</h3>
</div>
<div class="card-body table-responsive p-0">
<table class="table table-sm">
<caption style="display:none;">{{ title }}</caption>
<thead>
<tr>
<th scope="col">{{ $t('firefly.budget') }}</th>
<th scope="col">{{ $t('firefly.spent') }}</th>
<th scope="col">{{ $t('firefly.left') }}</th>
</tr>
</thead>
<tbody>
<BudgetLimitRow v-for="(budgetLimit, key) in budgetLimits" v-bind:key="key" :budgetLimit="budgetLimit"/>
<BudgetRow v-for="(budget, key) in budgets" v-bind:key="key" :budget="budget"/>
</tbody>
</table>
</div>
<div class="card-footer">
<a class="btn btn-default button-sm" href="./budgets"><span class="far fa-money-bill-alt"></span> {{ $t('firefly.go_to_budgets') }}</a>
</div>
</div>
</template>
<script>
import BudgetLimitRow from "./BudgetLimitRow";
import BudgetRow from "./BudgetRow";
export default {
name: "BudgetListGroup",
components: {BudgetLimitRow, BudgetRow},
props: {
title: String,
budgetLimits: Array,
budgets: Array,
},
}
</script>

View File

@@ -1,55 +0,0 @@
<!--
- BudgetRow.vue
- Copyright (c) 2020 james@firefly-iii.org
-
- This file is part of Firefly III (https://github.com/firefly-iii).
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<tr>
<td style="width:25%;">
<a :href="'./budgets/show/' + budget.id">{{ budget.name }}</a>
</td>
<td>&nbsp;</td>
<td class="align-middle text-right">
<span class="text-danger">
{{ Intl.NumberFormat(locale, {style: 'currency', currency: budget.currency_code}).format(parseFloat(budget.spent)) }}
</span>
</td>
</tr>
</template>
<script>
export default {
name: "BudgetRow",
created() {
this.locale = localStorage.locale ?? 'en-US';
},
data() {
return {
locale: 'en-US',
}
},
props: {
budget: {
type: Object,
default: {}
}
}
}
</script>

View File

@@ -1,602 +0,0 @@
<!--
- Calendar.vue
- Copyright (c) 2020 james@firefly-iii.org
-
- This file is part of Firefly III (https://github.com/firefly-iii).
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div>
<div class="row">
<div class="col">Start</div>
<div class="col-8">{{ new Intl.DateTimeFormat(locale, {year: 'numeric', month: 'long', day: 'numeric'}).format(range.start) }}</div>
</div>
<div class="row">
<div class="col">End</div>
<div class="col-8">{{ new Intl.DateTimeFormat(locale, {year: 'numeric', month: 'long', day: 'numeric'}).format(range.end) }}</div>
</div>
<date-picker
v-model="range"
:rows="2"
is-range
mode="date"
>
<template v-slot="{ inputValue, inputEvents, isDragging, togglePopover }">
<div class="row">
<div class="col">
<div class="btn-group btn-group-sm d-flex">
<button
:title="$t('firefly.custom_period')" class="btn btn-secondary btn-sm"
@click="togglePopover({ placement: 'auto-start', positionFixed: true })"
><span class="fas fa-calendar-alt"></span></button>
<button :title="$t('firefly.reset_to_current')"
class="btn btn-secondary"
@click="resetDate"
><span class="fas fa-history"></span></button>
<button id="dropdownMenuButton" :title="$t('firefly.select_period')" aria-expanded="false" aria-haspopup="true"
class="btn btn-secondary dropdown-toggle"
data-toggle="dropdown"
type="button">
<span class="fas fa-list"></span>
</button>
<div aria-labelledby="dropdownMenuButton" class="dropdown-menu">
<a v-for="period in periods" class="dropdown-item" href="#" @click="customDate(period.start, period.end)">{{ period.title }}</a>
</div>
</div>
<input v-on="inputEvents.start"
:class="isDragging ? 'text-gray-600' : 'text-gray-900'"
:value="inputValue.start"
type="hidden"
/>
<input v-on="inputEvents.end"
:class="isDragging ? 'text-gray-600' : 'text-gray-900'"
:value="inputValue.end"
type="hidden"
/>
</div>
</div>
</template>
</date-picker>
</div>
</template>
<script>
import {createNamespacedHelpers} from "vuex";
import Vue from "vue";
import DatePicker from "v-calendar/lib/components/date-picker.umd";
import subDays from 'date-fns/subDays';
import addDays from 'date-fns/addDays';
import addMonths from 'date-fns/addMonths';
import startOfDay from 'date-fns/startOfDay';
import endOfDay from 'date-fns/endOfDay';
import startOfWeek from 'date-fns/startOfWeek';
import endOfWeek from 'date-fns/endOfWeek';
import endOfMonth from 'date-fns/endOfMonth';
import format from 'date-fns/format';
import startOfQuarter from 'date-fns/startOfQuarter';
import subMonths from 'date-fns/subMonths';
import endOfQuarter from 'date-fns/endOfQuarter';
import subQuarters from 'date-fns/subQuarters';
import addQuarters from 'date-fns/addQuarters';
import startOfMonth from 'date-fns/startOfMonth';
const {mapState, mapGetters, mapActions, mapMutations} = createNamespacedHelpers('dashboard/index')
Vue.component('date-picker', DatePicker)
export default {
name: "Calendar",
created() {
// console.log('Now in calendar created');
this.ready = true;
this.locale = localStorage.locale ?? 'en-US';
},
data() {
return {
locale: 'en-US',
ready: false,
range: {
start: null,
end: null,
},
defaultRange: {
start: null,
end: null,
},
periods: []
};
},
methods: {
...mapMutations(
[
'setEnd',
'setStart',
],
),
resetDate: function () {
// console.log('Reset date to');
// console.log(this.defaultStart);
// console.log(this.defaultEnd);
this.range.start = this.defaultStart;
this.range.end = this.defaultEnd;
this.setStart(this.defaultStart);
this.setEnd(this.defaultEnd);
},
customDate: function (startStr, endStr) {
let start = new Date(startStr);
let end = new Date(endStr);
this.setStart(start);
this.setEnd(end);
this.range.start = start;
this.range.end = end;
this.generatePeriods()
return false;
},
generateDaily: function () {
let today = new Date(this.range.start);
// yesterday
this.periods.push(
{
start: startOfDay(subDays(today, 1)).toDateString(),
end: endOfDay(subDays(today, 1)).toDateString(),
title: new Intl.DateTimeFormat(this.locale, {year: 'numeric', month: 'long', day: 'numeric'}).format(subDays(today, 1))
}
);
// today
this.periods.push(
{
start: startOfDay(today).toDateString(),
end: endOfDay(today).toDateString(),
title: new Intl.DateTimeFormat(this.locale, {year: 'numeric', month: 'long', day: 'numeric'}).format(today)
}
);
// tomorrow:
this.periods.push(
{
start: startOfDay(addDays(today, 1)).toDateString(),
end: endOfDay(addDays(today, 1)).toDateString(),
title: new Intl.DateTimeFormat(this.locale, {year: 'numeric', month: 'long', day: 'numeric'}).format(addDays(today, 1))
}
);
// The Day After Tomorrow dun-dun-dun!
this.periods.push(
{
start: startOfDay(addDays(today, 2)).toDateString(),
end: endOfDay(addDays(today, 2)).toDateString(),
title: new Intl.DateTimeFormat(this.locale, {year: 'numeric', month: 'long', day: 'numeric'}).format(addDays(today, 2))
}
);
},
generateWeekly: function () {
//console.log('weekly');
let today = new Date(this.range.start);
//console.log('Today is ' + today);
let start = startOfDay(startOfWeek(subDays(today, 7), {weekStartsOn: 1}));
let end = endOfDay(endOfWeek(subDays(today, 7), {weekStartsOn: 1}));
let dateFormat = this.$t('config.week_in_year_fns');
//console.log('Date format: "'+dateFormat+'"');
let title = format(start, dateFormat);
// last week
// console.log('Last week');
// console.log(start);
// console.log(end);
// console.log(title);
this.periods.push(
{
start: start.toDateString(),
end: end.toDateString(),
title: title
}
);
// this week
start = startOfDay(startOfWeek(today, {weekStartsOn: 1}));
end = endOfDay(endOfWeek(today, {weekStartsOn: 1}));
title = format(start, dateFormat);
// console.log('This week');
// console.log(start);
// console.log(end);
// console.log(title);
this.periods.push(
{
start: start.toDateString(),
end: end.toDateString(),
title: title
}
);
// next week
start = startOfDay(startOfWeek(addDays(today, 7), {weekStartsOn: 1}));
end = endOfDay(endOfWeek(addDays(today, 7), {weekStartsOn: 1}));
title = format(start, dateFormat);
// console.log('Next week');
// console.log(start);
// console.log(end);
// console.log(title);
this.periods.push(
{
start: start.toDateString(),
end: end.toDateString(),
title: title
}
);
},
generateMonthly: function () {
let today = new Date(this.range.start);
// previous month
let start = startOfDay(startOfMonth(subMonths(today, 1)));
let end = endOfDay(endOfMonth(subMonths(today, 1)));
this.periods.push(
{
start: start.toDateString(),
end: end.toDateString(),
title: new Intl.DateTimeFormat(this.locale, {year: 'numeric', month: 'long'}).format(start)
}
);
// this month
start = startOfDay(startOfMonth(today));
end = endOfDay(endOfMonth(today));
this.periods.push(
{
start: start.toDateString(),
end: end.toDateString(),
title: new Intl.DateTimeFormat(this.locale, {year: 'numeric', month: 'long'}).format(start)
}
);
// next month
start = startOfDay(startOfMonth(addMonths(today, 1)));
end = endOfDay(endOfMonth(addMonths(today, 1)));
this.periods.push(
{
start: start.toDateString(),
end: end.toDateString(),
title: new Intl.DateTimeFormat(this.locale, {year: 'numeric', month: 'long'}).format(start)
}
);
},
generateQuarterly: function () {
let today = new Date(this.range.start);
// last quarter
let start = startOfDay(startOfQuarter(subQuarters(today, 1)));
let end = endOfDay(endOfQuarter(subQuarters(today, 1)));
let dateFormat = this.$t('config.quarter_fns');
let title = format(start, dateFormat);
// last week
this.periods.push(
{
start: start.toDateString(),
end: end.toDateString(),
title: title
}
);
// this quarter
start = startOfDay(startOfQuarter(today));
end = endOfDay(endOfQuarter(today));
title = format(start, dateFormat);
this.periods.push(
{
start: start.toDateString(),
end: end.toDateString(),
title: title
}
);
// next quarter
start = startOfDay(startOfQuarter(addQuarters(today, 1)));
end = endOfDay(endOfQuarter(addQuarters(today, 1)));
title = format(start, dateFormat);
this.periods.push(
{
start: start.toDateString(),
end: end.toDateString(),
title: title
}
);
},
generateHalfYearly: function () {
let today = new Date(this.range.start);
let start;
let end;
let title = 'tbd';
let half = 1;
// its currently first half of year:
if (today.getMonth() <= 5) {
// previous year, last half:
start = today;
start.setFullYear(start.getFullYear() - 1);
start.setMonth(6);
start.setDate(1);
start = startOfDay(start);
end = start;
end.setMonth(11);
end.setDate(31);
end = endOfDay(end);
half = 2;
title = format(start, this.$t('config.half_year_fns', {half: half}));
this.periods.push(
{
start: start.toDateString(),
end: end.toDateString(),
title: title
}
);
// this year, first half:
start = today;
start.setMonth(0);
start.setDate(1);
start = startOfDay(start);
end = today;
end.setMonth(5);
end.setDate(30);
end = endOfDay(start);
half = 1;
title = format(start, this.$t('config.half_year_fns', {half: half}));
this.periods.push(
{
start: start.toDateString(),
end: end.toDateString(),
title: title
}
);
// this year, second half:
start = today;
start.setMonth(6);
start.setDate(1);
start = startOfDay(start);
end = start;
end.setMonth(11);
end.setDate(31);
end = endOfDay(end);
half = 2;
title = format(start, this.$t('config.half_year_fns', {half: half}));
this.periods.push(
{
start: start.toDateString(),
end: end.toDateString(),
title: title
}
);
return;
}
// this year, first half:
start = today;
start.setMonth(0);
start.setDate(1);
start = startOfDay(start);
end = start;
end.setMonth(5);
end.setDate(30);
end = endOfDay(end);
title = format(start, this.$t('config.half_year_fns', {half: half}));
this.periods.push(
{
start: start.toDateString(),
end: end.toDateString(),
title: title
}
);
// this year, current (second) half:
start = today;
start.setMonth(6);
start.setDate(1);
start = startOfDay(start);
end = today;
end.setMonth(11);
end.setDate(31);
end = endOfDay(start);
half = 2;
title = format(start, this.$t('config.half_year_fns', {half: half}));
this.periods.push(
{
start: start.toDateString(),
end: end.toDateString(),
title: title
}
);
// next year, first half:
start = today;
start.setMonth(0);
start.setDate(1);
start = startOfDay(start);
end = start;
end.setMonth(5);
end.setDate(30);
end = endOfDay(end);
half = 1;
title = format(start, this.$t('config.half_year_fns', {half: half}));
this.periods.push(
{
start: start.toDateString(),
end: end.toDateString(),
title: title
}
);
},
generateYearly: function () {
let today = new Date(this.range.start);
let start;
let end;
// last year
start = new Date(today);
start.setFullYear(start.getFullYear() - 1);
start.setMonth(0);
start.setDate(1);
start = startOfDay(start);
end = new Date(today);
end.setFullYear(end.getFullYear() - 1);
end.setMonth(11);
end.setDate(31);
end = endOfDay(end);
this.periods.push(
{
start: start.toDateString(),
end: end.toDateString(),
title: start.getFullYear()
}
);
// this year
start = new Date(today);
start.setMonth(0);
start.setDate(1);
start = startOfDay(start);
end = new Date(today);
end.setMonth(11);
end.setDate(31);
end = endOfDay(end);
this.periods.push(
{
start: start.toDateString(),
end: end.toDateString(),
title: start.getFullYear()
}
);
// next year
start = new Date(today);
start.setFullYear(start.getFullYear() + 1);
start.setMonth(0);
start.setDate(1);
start = startOfDay(start);
end = new Date(today);
end.setFullYear(end.getFullYear() + 1);
end.setMonth(11);
end.setDate(31);
end = endOfDay(end);
this.periods.push(
{
start: start.toDateString(),
end: end.toDateString(),
title: start.getFullYear()
}
);
},
generatePeriods: function () {
this.periods = [];
// console.log('The view range is "' + this.viewRange + '".');
switch (this.viewRange) {
case '1D':
this.generateDaily();
break;
case '1W':
this.generateWeekly();
break;
case '1M':
this.generateMonthly();
break;
case '3M':
this.generateQuarterly();
break;
case '6M':
this.generateHalfYearly();
break;
case '1Y':
this.generateYearly();
break;
}
// last 7 days
let today = new Date;
let end = new Date;
end.setDate(end.getDate() - 7);
this.periods.push(
{
start: end.toDateString(),
end: today.toDateString(),
title: this.$t('firefly.last_seven_days')
}
);
// last 30 days:
end.setDate(end.getDate() - 23);
this.periods.push(
{
start: end.toDateString(),
end: today.toDateString(),
title: this.$t('firefly.last_thirty_days')
}
);
// last 30 days
// everything
}
},
computed: {
...mapGetters([
'viewRange',
'start',
'end',
'defaultStart',
'defaultEnd'
]),
'datesReady': function () {
return null !== this.start && null !== this.end && this.ready;
},
},
watch: {
datesReady: function (value) {
if (false === value) {
return;
}
this.range.start = new Date(this.start);
this.range.end = new Date(this.end);
this.generatePeriods();
},
range: function (value) {
//console.log('User updated range');
this.setStart(value.start);
this.setEnd(value.end);
}
}
}
</script>
<style scoped>
.dropdown-item {
color: #212529;
}
.dropdown-item:hover {
color: #212529;
}
</style>

View File

@@ -1,66 +0,0 @@
<!--
- ExampleComponent.vue
- 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/>.
-->
<template>
<div>
<top-boxes/>
<div class="row">
<div class="col">
<main-account/>
</div>
</div>
<main-account-list/>
<div class="row">
<div class="col">
<main-budget-list/>
</div>
</div>
<div class="row">
<div class="col">
<main-category-list/>
</div>
</div>
<div class="row">
<div class="col-lg-6 col-md-12 col-sm-12 col-xs-12">
<main-debit-list/>
</div>
<div class="col-lg-6 col-md-12 col-sm-12 col-xs-12">
<main-credit-list/>
</div>
</div>
<div class="row">
<div class="col-lg-6 col-md-12 col-sm-12 col-xs-12">
<main-piggy-list/>
</div>
<div class="col-lg-6 col-md-12 col-sm-12 col-xs-12">
<main-bills-list/>
</div>
</div>
</div>
</template>
<script>
export default {
name: "Dashboard",
}
</script>

View File

@@ -1,108 +0,0 @@
<!--
- DashboardListLarge.vue
- Copyright (c) 2020 james@firefly-iii.org
-
- This file is part of Firefly III (https://github.com/firefly-iii).
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<table class="table table-striped table-sm">
<caption style="display:none;">{{ $t('firefly.transaction_table_description') }}</caption>
<thead>
<tr>
<th class="text-left" scope="col">{{ $t('firefly.description') }}</th>
<th scope="col">{{ $t('firefly.opposing_account') }}</th>
<th class="text-right" scope="col">{{ $t('firefly.amount') }}</th>
<th scope="col">{{ $t('firefly.category') }}</th>
<th scope="col">{{ $t('firefly.budget') }}</th>
</tr>
</thead>
<tbody>
<tr v-for="transaction in this.transactions">
<td>
<a :href="'transactions/show/' + transaction.id " :title="transaction.date">
<span v-if="transaction.attributes.transactions.length > 1">{{ transaction.attributes.group_title }}</span>
<span v-if="1===transaction.attributes.transactions.length">{{ transaction.attributes.transactions[0].description }}</span>
</a>
</td>
<td>
<span v-for="tr in transaction.attributes.transactions">
<a v-if="'withdrawal' === tr.type" :href="'accounts/show/' + tr.destination_id">{{ tr.destination_name }}</a>
<a v-if="'deposit' === tr.type" :href="'accounts/show/' + tr.source_id">{{ tr.source_name }}</a>
<a v-if="'transfer' === tr.type && parseInt(tr.source_id) === account_id" :href="'accounts/show/' + tr.destination_id">{{ tr.destination_name }}</a>
<a v-if="'transfer' === tr.type && parseInt(tr.destination_id) === account_id" :href="'accounts/show/' + tr.source_id">{{ tr.source_name }}</a>
<br/>
</span>
</td>
<td style="text-align:right;">
<span v-for="tr in transaction.attributes.transactions">
<span v-if="'withdrawal' === tr.type" class="text-danger">
{{ Intl.NumberFormat(locale, {style: 'currency', currency: tr.currency_code}).format(tr.amount * -1) }}<br>
</span>
<span v-if="'deposit' === tr.type" class="text-success">
{{ Intl.NumberFormat(locale, {style: 'currency', currency: tr.currency_code}).format(tr.amount) }}<br>
</span>
<span v-if="'transfer' === tr.type && parseInt(tr.source_id) === account_id" class="text-info">
{{ Intl.NumberFormat(locale, {style: 'currency', currency: tr.currency_code}).format(tr.amount * -1) }}<br>
</span>
<span v-if="'transfer' === tr.type && parseInt(tr.destination_id) === account_id" class="text-info">
{{ Intl.NumberFormat(locale, {style: 'currency', currency: tr.currency_code}).format(tr.amount) }}<br>
</span>
</span>
</td>
<td>
<span v-for="tr in transaction.attributes.transactions">
<a v-if="0!==tr.category_id" :href="'categories/show/' + tr.category_id">{{ tr.category_name }}</a><br/>
</span>
</td>
<td>
<span v-for="tr in transaction.attributes.transactions">
<a v-if="0!==tr.budget_id" :href="'budgets/show/' + tr.budget_id">{{ tr.budget_name }}</a><br/>
</span>
</td>
</tr>
</tbody>
</table>
</template>
<script>
export default {
name: "DashboardListLarge",
data() {
return {
locale: 'en-US'
}
},
created() {
this.locale = localStorage.locale ?? 'en-US';
},
props: {
transactions: {
type: Array,
default: function () {
return [];
}
},
account_id: {
type: Number,
default: function () {
return 0;
}
},
}
}
</script>

View File

@@ -1,96 +0,0 @@
<!--
- DashboardListMedium.vue
- Copyright (c) 2020 james@firefly-iii.org
-
- This file is part of Firefly III (https://github.com/firefly-iii).
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<table class="table table-striped table-sm">
<caption style="display:none;">{{ $t('firefly.transaction_table_description') }}</caption>
<thead>
<tr>
<th class="text-left" scope="col">{{ $t('firefly.description') }}</th>
<th scope="col">{{ $t('firefly.opposing_account') }}</th>
<th class="text-right" scope="col">{{ $t('firefly.amount') }}</th>
</tr>
</thead>
<tbody>
<tr v-for="transaction in this.transactions">
<td>
<a :href="'transactions/show/' + transaction.id " :title="transaction.date">
<span v-if="transaction.attributes.transactions.length > 1">{{ transaction.attributes.group_title }}</span>
<span v-if="1===transaction.attributes.transactions.length">{{ transaction.attributes.transactions[0].description }}</span>
</a>
</td>
<td>
<span v-for="tr in transaction.attributes.transactions">
<a v-if="'withdrawal' === tr.type" :href="'accounts/show/' + tr.destination_id">{{ tr.destination_name }}</a>
<a v-if="'deposit' === tr.type" :href="'accounts/show/' + tr.source_id">{{ tr.source_name }}</a>
<a v-if="'transfer' === tr.type && parseInt(tr.source_id) === account_id" :href="'accounts/show/' + tr.destination_id">{{ tr.destination_name }}</a>
<a v-if="'transfer' === tr.type && parseInt(tr.destination_id) === account_id" :href="'accounts/show/' + tr.source_id">{{ tr.source_name }}</a>
<br/>
</span>
</td>
<td style="text-align:right;">
<span v-for="tr in transaction.attributes.transactions">
<span v-if="'withdrawal' === tr.type" class="text-danger">
{{ Intl.NumberFormat(locale, {style: 'currency', currency: tr.currency_code}).format(tr.amount * -1) }}<br>
</span>
<span v-if="'deposit' === tr.type" class="text-success">
{{ Intl.NumberFormat(locale, {style: 'currency', currency: tr.currency_code}).format(tr.amount) }}<br>
</span>
<span v-if="'transfer' === tr.type && parseInt(tr.source_id) === account_id" class="text-info">
{{ Intl.NumberFormat(locale, {style: 'currency', currency: tr.currency_code}).format(tr.amount * -1) }}<br>
</span>
<span v-if="'transfer' === tr.type && parseInt(tr.destination_id) === account_id" class="text-info">
{{ Intl.NumberFormat(locale, {style: 'currency', currency: tr.currency_code}).format(tr.amount) }}<br>
</span>
</span>
</td>
</tr>
</tbody>
</table>
</template>
<script>
export default {
name: "DashboardListMedium",
data() {
return {
locale: 'en-US'
}
},
created() {
this.locale = localStorage.locale ?? 'en-US';
},
props: {
transactions: {
type: Array,
default: function () {
return [];
}
},
account_id: {
type: Number,
default: function () {
return 0;
}
},
}
}
</script>

View File

@@ -1,88 +0,0 @@
<!--
- DashboardListSmall.vue
- Copyright (c) 2020 james@firefly-iii.org
-
- This file is part of Firefly III (https://github.com/firefly-iii).
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<table class="table table-striped table-sm">
<caption style="display:none;">{{ $t('firefly.transaction_table_description') }}</caption>
<thead>
<tr>
<th class="text-left" scope="col">{{ $t('firefly.description') }}</th>
<th class="text-right" scope="col">{{ $t('firefly.amount') }}</th>
</tr>
</thead>
<tbody>
<tr v-for="transaction in this.transactions">
<td>
<a :href="'transactions/show/' + transaction.id "
:title="new Intl.DateTimeFormat(locale, { year: 'numeric', month: 'long', day: 'numeric' }).format(new Date(transaction.attributes.transactions[0].date))">
<span v-if="transaction.attributes.transactions.length > 1">{{ transaction.attributes.group_title }}</span>
<span v-if="1===transaction.attributes.transactions.length">{{ transaction.attributes.transactions[0].description }}</span>
</a>
</td>
<td style="text-align:right;">
<span v-for="tr in transaction.attributes.transactions">
<span v-if="'withdrawal' === tr.type" class="text-danger">
{{ Intl.NumberFormat(locale, {style: 'currency', currency: tr.currency_code}).format(tr.amount * -1) }}<br>
</span>
<span v-if="'deposit' === tr.type" class="text-success">
{{ Intl.NumberFormat(locale, {style: 'currency', currency: tr.currency_code}).format(tr.amount) }}<br>
</span>
<span v-if="'transfer' === tr.type && parseInt(tr.source_id) === account_id" class="text-info">
{{ Intl.NumberFormat(locale, {style: 'currency', currency: tr.currency_code}).format(tr.amount * -1) }}<br>
</span>
<span v-if="'transfer' === tr.type && parseInt(tr.destination_id) === account_id" class="text-info">
{{ Intl.NumberFormat(locale, {style: 'currency', currency: tr.currency_code}).format(tr.amount) }}<br>
</span>
</span>
</td>
</tr>
</tbody>
</table>
</template>
<script>
export default {
name: "DashboardListSmall",
data() {
return {
locale: 'en-US'
}
},
created() {
this.locale = localStorage.locale ?? 'en-US';
},
methods: {},
props: {
transactions: {
type: Array,
default: function () {
return [];
}
},
account_id: {
type: Number,
default: function () {
return 0;
}
},
}
}
</script>

View File

@@ -1,146 +0,0 @@
<!--
- MainAccount.vue
- Copyright (c) 2020 james@firefly-iii.org
-
- This file is part of Firefly III (https://github.com/firefly-iii).
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div class="card">
<div class="card-header">
<h3 class="card-title">{{ $t('firefly.yourAccounts') }}</h3>
</div>
<div class="card-body">
<div>
<canvas id="canvas" ref="canvas" width="400" height="400"></canvas>
</div>
<div v-if="loading && !error" class="text-center">
<span class="fas fa-spinner fa-spin"></span>
</div>
<div v-if="error" class="text-center">
<span class="fas fa-exclamation-triangle text-danger"></span>
</div>
</div>
<div class="card-footer">
<a class="btn btn-default button-sm" href="./accounts/asset"><span class="far fa-money-bill-alt"></span> {{ $t('firefly.go_to_asset_accounts') }}</a>
</div>
</div>
</template>
<script>
import DataConverter from "../charts/DataConverter";
import DefaultLineOptions from "../charts/DefaultLineOptions";
import {mapGetters} from "vuex";
import * as ChartJs from 'chart.js'
import format from "date-fns/format";
ChartJs.Chart.register.apply(null, Object.values(ChartJs).filter((chartClass) => (chartClass.id)));
export default {
name: "MainAccount",
components: {}, // MainAccountChart
data() {
return {
loading: true,
error: false,
ready: false,
initialised: false,
dataCollection: {},
chartOptions: {},
_chart: null,
}
},
created() {
this.chartOptions = DefaultLineOptions.methods.getDefaultOptions();
this.ready = true;
},
computed: {
...mapGetters('dashboard/index', ['start', 'end']),
'datesReady': function () {
return null !== this.start && null !== this.end && this.ready;
}
},
watch: {
datesReady: function (value) {
if (true === value) {
this.initialiseChart();
}
},
start: function () {
this.updateChart();
},
end: function () {
this.updateChart();
},
},
methods: {
initialiseChart: function () {
this.loading = true;
this.error = false;
//let startStr = this.start.toISOString().split('T')[0];
//let endStr = this.end.toISOString().split('T')[0];
let startStr = format(this.start, 'y-MM-dd');
let endStr = format(this.end, 'y-MM-dd');
let url = './api/v1/chart/account/overview?start=' + startStr + '&end=' + endStr;
axios.get(url)
.then(response => {
let chartData = DataConverter.methods.convertChart(response.data);
chartData = DataConverter.methods.colorizeLineData(chartData);
this.dataCollection = chartData;
this.loading = false;
this.drawChart();
})
.catch(error => {
console.error('Has error!');
console.error(error);
this.error = true;
});
},
drawChart: function () {
//console.log('drawChart');
if ('undefined' !== typeof this._chart) {
// console.log('update!');
this._chart.data = this.dataCollection;
this._chart.update();
this.initialised = true;
}
if ('undefined' === typeof this._chart) {
// console.log('new!');
this._chart = new ChartJs.Chart(this.$refs.canvas.getContext('2d'), {
type: 'line',
data: this.dataCollection,
options: this.chartOptions
}
);
this.initialised = true;
}
},
updateChart: function () {
// console.log('updateChart');
if (this.initialised) {
// console.log('MUST Update chart!');
// reset some vars so it wont trigger again:
this.initialised = false;
this.initialiseChart();
}
}
},
}
</script>

View File

@@ -1,35 +0,0 @@
<!--
- MainAccountChart.vue
- Copyright (c) 2020 james@firefly-iii.org
-
- This file is part of Firefly III (https://github.com/firefly-iii).
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<script>
//import {Line, mixins} from 'vue-chartjs'
const {reactiveProp} = mixins
export default {
extends: Line,
mixins: [reactiveProp],
props: ['options'],
mounted() {
this.renderChart(this.chartData, this.options)
}
}
</script>

View File

@@ -1,181 +0,0 @@
<!--
- MainAccountList.vue
- Copyright (c) 2020 james@firefly-iii.org
-
- This file is part of Firefly III (https://github.com/firefly-iii).
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div>
<!-- row if loading -->
<div v-if="loading && !error" class="row">
<div class="col">
<div class="card">
<div class="card-body">
<div class="text-center">
<span class="fas fa-spinner fa-spin"></span>
</div>
</div>
</div>
</div>
</div>
<!-- row if error -->
<div v-if="error" class="row">
<div class="col">
<div class="card">
<div class="card-body">
<div class="text-center">
<span class="fas fa-exclamation-triangle text-danger"></span>
</div>
</div>
</div>
</div>
</div>
<!-- row if normal -->
<div v-if="!loading && !error" class="row">
<div
v-for="account in accounts"
v-bind:class="{ 'col-lg-12': 1 === accounts.length, 'col-lg-6': 2 === accounts.length, 'col-lg-4': accounts.length > 2 }">
<div class="card">
<div class="card-header">
<h3 class="card-title"><a :href="account.url">{{ account.title }}</a></h3>
<div class="card-tools">
<span :class="parseFloat(account.current_balance) < 0 ? 'text-danger' : 'text-success'">
{{ Intl.NumberFormat(locale, {style: 'currency', currency: account.currency_code}).format(parseFloat(account.current_balance)) }}
</span>
</div>
</div>
<div class="card-body table-responsive p-0">
<div>
<dashboard-list-large v-if="1===accounts.length" :account_id="account.id" :transactions="account.transactions"/>
<dashboard-list-medium v-if="2===accounts.length" :account_id="account.id" :transactions="account.transactions"/>
<dashboard-list-small v-if="accounts.length > 2" :account_id="account.id" :transactions="account.transactions"/>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import {createNamespacedHelpers} from "vuex";
import format from "date-fns/format";
const {mapState, mapGetters, mapActions, mapMutations} = createNamespacedHelpers('dashboard/index')
export default {
name: "MainAccountList",
data() {
return {
loading: true,
error: false,
ready: false,
accounts: [],
locale: 'en-US'
}
},
created() {
this.locale = localStorage.locale ?? 'en-US';
this.ready = true;
},
computed: {
...mapGetters([
'start',
'end'
]),
'datesReady': function () {
return null !== this.start && null !== this.end && this.ready;
},
},
watch: {
datesReady: function (value) {
if (true === value) {
this.initialiseList();
}
},
start: function () {
if (false === this.loading) {
this.initialiseList();
}
},
end: function () {
if (false === this.loading) {
this.initialiseList();
}
},
},
methods: {
initialiseList: function () {
this.loading = true;
this.accounts = [];
axios.get('./api/v1/preferences/frontPageAccounts')
.then(response => {
this.loadAccounts(response);
}
);
},
loadAccounts(response) {
let accountIds = response.data.data.attributes.data;
for (let key in accountIds) {
if (accountIds.hasOwnProperty(key) && /^0$|^[1-9]\d*$/.test(key) && key <= 4294967294) {
this.accounts.push({
id: accountIds[key],
title: '',
url: '',
include: false,
current_balance: '0',
currency_code: 'EUR',
transactions: []
});
this.loadSingleAccount(key, accountIds[key]);
}
}
},
loadSingleAccount(key, accountId) {
axios.get('./api/v1/accounts/' + accountId)
.then(response => {
let account = response.data.data;
if ('asset' === account.attributes.type || 'liabilities' === account.attributes.type) {
this.accounts[key].title = account.attributes.name;
this.accounts[key].url = './accounts/show/' + account.id;
this.accounts[key].current_balance = account.attributes.current_balance;
this.accounts[key].currency_code = account.attributes.currency_code;
this.accounts[key].include = true;
this.loadTransactions(key, accountId);
}
}
);
},
loadTransactions(key, accountId) {
// let startStr = this.start.toISOString().split('T')[0];
// let endStr = this.end.toISOString().split('T')[0];
let startStr = format(this.start, 'y-MM-dd');
let endStr = format(this.end, 'y-MM-dd');
axios.get('./api/v1/accounts/' + accountId + '/transactions?page=1&limit=10&start=' + startStr + '&end=' + endStr)
.then(response => {
this.accounts[key].transactions = response.data.data;
this.loading = false;
this.error = false;
}
);
},
}
}
</script>

View File

@@ -1,164 +0,0 @@
<!--
- MainBills.vue
- Copyright (c) 2020 james@firefly-iii.org
-
- This file is part of Firefly III (https://github.com/firefly-iii).
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div class="card">
<div class="card-header">
<h3 class="card-title">{{ $t('firefly.bills') }}</h3>
</div>
<!-- body if loading -->
<div v-if="loading && !error" class="card-body">
<div class="text-center">
<span class="fas fa-spinner fa-spin"></span>
</div>
</div>
<!-- body if error -->
<div v-if="error" class="card-body">
<div class="text-center">
<span class="fas fa-exclamation-triangle text-danger"></span>
</div>
</div>
<!-- body if normal -->
<div v-if="!loading && !error" class="card-body table-responsive p-0">
<table class="table table-striped">
<caption style="display:none;">{{ $t('firefly.bills') }}</caption>
<thead>
<tr>
<th scope="col" style="width:35%;">{{ $t('list.name') }}</th>
<th scope="col" style="width:25%;">{{ $t('list.next_expected_match') }}</th>
</tr>
</thead>
<tbody>
<tr v-for="bill in this.bills">
<td><a :href="'./bills/show/' + bill.id" :title="bill.attributes.name">{{ bill.attributes.name }}</a>
(~ <span class="text-danger">{{
Intl.NumberFormat(locale, {style: 'currency', currency: bill.attributes.currency_code}).format((parseFloat(bill.attributes.amount_min) +
parseFloat(bill.attributes.amount_max)) / -2)
}}</span>)
<small v-if="bill.attributes.object_group_title" class="text-muted">
<br/>
{{ bill.attributes.object_group_title }}
</small>
</td>
<td>
<span v-for="paidDate in bill.attributes.paid_dates">
<span v-html="renderPaidDate(paidDate)"/><br/>
</span>
<span v-for="payDate in bill.attributes.pay_dates" v-if="0===bill.attributes.paid_dates.length">
{{ new Intl.DateTimeFormat(locale, {year: 'numeric', month: 'long', day: 'numeric'}).format(new Date(payDate)) }}
<br/>
</span>
</td>
</tr>
</tbody>
</table>
</div>
<div class="card-footer">
<a class="btn btn-default button-sm" href="./bills"><span class="far fa-money-bill-alt"></span> {{ $t('firefly.go_to_bills') }}</a>
</div>
</div>
</template>
<script>
import {createNamespacedHelpers} from "vuex";
import format from "date-fns/format";
const {mapState, mapGetters, mapActions, mapMutations} = createNamespacedHelpers('dashboard/index')
export default {
name: "MainBillsList",
data() {
return {
bills: [],
locale: 'en-US',
ready: false,
loading: true,
error: false
}
},
computed: {
...mapGetters([
'start',
'end'
]),
'datesReady': function () {
return null !== this.start && null !== this.end && this.ready;
}
},
watch: {
datesReady: function (value) {
if (true === value) {
this.initialiseBills();
}
},
start: function () {
if (false === this.loading) {
this.initialiseBills();
}
},
end: function () {
if (false === this.loading) {
this.initialiseBills();
}
},
},
created() {
this.ready = true;
this.locale = localStorage.locale ?? 'en-US';
},
components: {},
methods: {
initialiseBills: function () {
this.loading = true;
this.bills = [];
// let startStr = this.start.toISOString().split('T')[0];
// let endStr = this.end.toISOString().split('T')[0];
let startStr = format(this.start, 'y-MM-dd');
let endStr = format(this.end, 'y-MM-dd');
axios.get('./api/v1/bills?start=' + startStr + '&end=' + endStr)
.then(response => {
this.loadBills(response.data.data);
}
).catch(error => {
this.error = true;
this.loading = false;
});
},
renderPaidDate: function (obj) {
let dateStr = new Intl.DateTimeFormat(this.locale, {year: 'numeric', month: 'long', day: 'numeric'}).format(new Date(obj.date));
let str = this.$t('firefly.bill_paid_on', {date: dateStr});
return '<a href="./transactions/show/' + obj.transaction_group_id + '" title="' + str + '">' + str + '</a>';
},
loadBills: function (data) {
for (let key in data) {
if (data.hasOwnProperty(key) && /^0$|^[1-9]\d*$/.test(key) && key <= 4294967294) {
let bill = data[key];
let active = bill.attributes.active;
if (bill.attributes.pay_dates.length > 0 && active) {
this.bills.push(bill);
}
}
}
this.error = false;
this.loading = false;
}
}
}
</script>

View File

@@ -1,278 +0,0 @@
<!--
- MainBudgetList.vue
- Copyright (c) 2020 james@firefly-iii.org
-
- This file is part of Firefly III (https://github.com/firefly-iii).
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div>
<!-- daily budgets (will be the exception, I expect) -->
<div v-if="!loading" class="row">
<div v-if="budgetLimits.daily.length > 0" class="col-xl-6 col-lg-12 col-md-12 col-sm-12 col-xs-12">
<BudgetListGroup :budgetLimits=budgetLimits.daily :title="$t('firefly.daily_budgets')"
/>
</div>
<div v-if="budgetLimits.weekly.length > 0" class="col-xl-6 col-lg-12 col-md-12 col-sm-12 col-xs-12">
<BudgetListGroup :budgetLimits=budgetLimits.weekly :title="$t('firefly.weekly_budgets')"
/>
</div>
<div v-if="budgetLimits.monthly.length > 0" class="col-xl-6 col-lg-12 col-md-12 col-sm-12 col-xs-12">
<BudgetListGroup :budgetLimits=budgetLimits.monthly :title="$t('firefly.monthly_budgets')"
/>
</div>
<div v-if="budgetLimits.quarterly.length > 0" class="col-xl-6 col-lg-12 col-md-12 col-sm-12 col-xs-12">
<BudgetListGroup :budgetLimits=budgetLimits.quarterly :title="$t('firefly.quarterly_budgets')"
/>
</div>
<div v-if="budgetLimits.half_year.length > 0" class="col-xl-6 col-lg-12 col-md-12 col-sm-12 col-xs-12">
<BudgetListGroup :budgetLimits=budgetLimits.half_year :title="$t('firefly.half_year_budgets')"
/>
</div>
<div v-if="budgetLimits.yearly.length > 0" class="col-xl-6 col-lg-12 col-md-12 col-sm-12 col-xs-12">
<BudgetListGroup :budgetLimits=budgetLimits.yearly :title="$t('firefly.yearly_budgets')"
/>
</div>
<div v-if="budgetLimits.other.length > 0 || rawBudgets.length > 0" class="col-xl-6 col-lg-12 col-md-12 col-sm-12 col-xs-12">
<BudgetListGroup :budgetLimits=budgetLimits.other :budgets="rawBudgets" :title="$t('firefly.other_budgets')"
/>
</div>
</div>
<div v-if="loading && !error" class="row">
<div class="col">
<div class="card">
<div class="card-body">
<div class="text-center">
<span class="fas fa-spinner fa-spin"></span>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import BudgetListGroup from "./BudgetListGroup";
import {createNamespacedHelpers} from "vuex";
import format from "date-fns/format";
const {mapState, mapGetters, mapActions, mapMutations} = createNamespacedHelpers('dashboard/index')
export default {
name: "MainBudgetList",
components: {BudgetListGroup},
data() {
return {
budgetList: ['daily', 'weekly', 'monthly', 'quarterly', 'half_year', 'yearly', 'other'],
budgetLimits: {
daily: [],
weekly: [],
monthly: [],
quarterly: [],
half_year: [],
yearly: [],
other: [],
},
budgets: {}, // used to collect some meta data.
rawBudgets: [],
locale: 'en-US',
ready: false,
loading: true,
error: false
}
},
created() {
this.ready = true;
this.locale = localStorage.locale ?? 'en-US';
},
watch: {
datesReady: function (value) {
if (true === value) {
this.getBudgets();
}
},
start: function () {
if (false === this.loading) {
this.getBudgets();
}
},
end: function () {
if (false === this.loading) {
this.getBudgets();
}
},
},
computed: {
...mapGetters(['start', 'end']),
'datesReady': function () {
return null !== this.start && null !== this.end && this.ready;
}
},
methods:
{
getBudgets: function () {
this.budgets = {};
this.rawBudgets = [];
this.budgetLimits = {
daily: [],
weekly: [],
monthly: [],
quarterly: [],
half_year: [],
yearly: [],
other: [],
};
this.loading = true;
// let startStr = this.start.toISOString().split('T')[0];
// let endStr = this.end.toISOString().split('T')[0];
let startStr = format(this.start, 'y-MM-dd');
let endStr = format(this.end, 'y-MM-dd');
axios.get('./api/v1/budgets?start=' + startStr + '&end=' + endStr)
.then(response => {
this.parseBudgets(response.data);
}
);
},
parseBudgets(data) {
for (let i in data.data) {
if (data.data.hasOwnProperty(i) && /^0$|^[1-9]\d*$/.test(i) && i <= 4294967294) {
let current = data.data[i];
if (false === current.attributes.active) {
// skip inactive budgets
continue;
}
for (let ii in current.attributes.spent) {
if (current.attributes.spent.hasOwnProperty(ii) && /^0$|^[1-9]\d*$/.test(ii) && ii <= 4294967294) {
let spentData = current.attributes.spent[ii];
this.rawBudgets.push(
{
id: parseInt(current.id),
name: current.attributes.name,
currency_id: parseInt(spentData.currency_id),
currency_code: spentData.currency_code,
spent: spentData.sum
}
);
//console.log('Added budget ' + current.attributes.name + ' (' + spentData.currency_code + ')');
}
}
}
}
this.getBudgetLimits();
},
getBudgetLimits() {
// let startStr = this.start.toISOString().split('T')[0];
// let endStr = this.end.toISOString().split('T')[0];
let startStr = format(this.start, 'y-MM-dd');
let endStr = format(this.end, 'y-MM-dd');
axios.get('./api/v1/budget-limits?start=' + startStr + '&end=' + endStr)
.then(response => {
this.parseBudgetLimits(response.data);
this.loading = false;
}
);
},
parseBudgetLimits(data) {
// collect budget meta data.
for (let i in data.included) {
if (data.included.hasOwnProperty(i) && /^0$|^[1-9]\d*$/.test(i) && i <= 4294967294) {
let current = data.included[i];
let currentId = parseInt(current.id);
this.budgets[currentId] =
{
id: currentId,
name: current.attributes.name,
};
//console.log('Collected meta data: budget #' + currentId + ' is named ' + current.attributes.name);
}
}
for (let i in data.data) {
if (data.data.hasOwnProperty(i) && /^0$|^[1-9]\d*$/.test(i) && i <= 4294967294) {
let current = data.data[i];
let currentId = parseInt(current.id);
let budgetId = parseInt(current.attributes.budget_id);
let currencyId = parseInt(current.attributes.currency_id);
let spentFloat = parseFloat(current.attributes.spent);
let spentFloatPos = parseFloat(current.attributes.spent) * -1;
let amount = parseFloat(current.attributes.amount);
let period = current.attributes.period ?? 'other';
let pctGreen = 0;
let pctOrange = 0;
let pctRed = 0;
//console.log('Collected "' + period + '" budget limit #' + currentId + ' (part of budget #' + budgetId + ')');
// console.log('Spent ' + spentFloatPos + ' of ' + amount);
// remove budget info from rawBudgets if it's there:
this.filterBudgets(budgetId, currencyId);
// let name = this.budgets[current.attributes.budget_id].name;
// spent within budget:
if (0.0 !== spentFloat && spentFloatPos < amount) {
// console.log('Spent ' + name + ' in budget');
pctGreen = (spentFloatPos / amount) * 100;
// console.log('pctGreen is ' + pctGreen);
}
// spent over budget
if (0.0 !== spentFloatPos && spentFloatPos > amount) {
//console.log('Spent ' + name + ' OVER budget');
pctOrange = (amount / spentFloatPos) * 100;
pctRed = 100 - pctOrange;
//console.log('orange is ' + pctOrange);
//console.log('red is ' + pctRed);
}
// spent exactly on budget
if (0.0 !== spentFloatPos && spentFloatPos === amount) {
pctOrange = 100;
pctRed = 0;
}
let obj = {
id: currentId,
amount: current.attributes.amount,
budget_id: budgetId,
budget_name: this.budgets[current.attributes.budget_id].name,
currency_id: currencyId,
currency_code: current.attributes.currency_code,
period: current.attributes.period,
start: new Date(current.attributes.start),
end: new Date(current.attributes.end),
spent: current.attributes.spent,
pctGreen: pctGreen,
pctOrange: pctOrange,
pctRed: pctRed,
};
this.budgetLimits[period].push(obj);
}
}
},
filterBudgets(budgetId, currencyId) {
for (let i in this.rawBudgets) {
if (this.rawBudgets.hasOwnProperty(i) && /^0$|^[1-9]\d*$/.test(i) && i <= 4294967294) {
if (this.rawBudgets[i].currency_id === currencyId && this.rawBudgets[i].id === budgetId) {
//console.log('Budget ' + this.rawBudgets[i].name + ' with currency ' + this.rawBudgets[i].currency_code + ' will be removed in favor of a budget limit.');
this.rawBudgets.splice(parseInt(i), 1);
}
}
}
}
}
}
</script>

View File

@@ -1,250 +0,0 @@
<!--
- MainCategoryList.vue
- Copyright (c) 2020 james@firefly-iii.org
-
- This file is part of Firefly III (https://github.com/firefly-iii).
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div class="card">
<div class="card-header">
<h3 class="card-title">{{ $t('firefly.categories') }}</h3>
</div>
<!-- body if loading -->
<div v-if="loading && !error" class="card-body">
<div class="text-center">
<span class="fas fa-spinner fa-spin"></span>
</div>
</div>
<!-- body if error -->
<div v-if="error" class="card-body">
<div class="text-center">
<span class="fas fa-exclamation-triangle text-danger"></span>
</div>
</div>
<!-- body if normal -->
<div v-if="!loading && !error" class="card-body table-responsive p-0">
<table class="table table-sm">
<caption style="display:none;">{{ $t('firefly.categories') }}</caption>
<thead>
<tr>
<th scope="col">{{ $t('firefly.category') }}</th>
<th scope="col">{{ $t('firefly.spent') }} / {{ $t('firefly.earned') }}</th>
</tr>
</thead>
<tbody>
<tr v-for="category in sortedList">
<td style="width:20%;">
<a :href="'./categories/show/' + category.id">{{ category.name }}</a>
</td>
<td class="align-middle">
<!-- SPENT -->
<div v-if="category.spentPct > 0" class="progress">
<div :aria-valuenow="category.spentPct" :style="{ width: category.spentPct + '%'}" aria-valuemax="100"
aria-valuemin="0" class="progress-bar progress-bar-striped bg-danger"
role="progressbar">
<span v-if="category.spentPct > 20">
{{ Intl.NumberFormat(locale, {style: 'currency', currency: category.currency_code}).format(category.spent) }}
</span>
</div>
<span v-if="category.spentPct <= 20" class="progress-label" style="line-height: 16px;">&nbsp;
{{ Intl.NumberFormat(locale, {style: 'currency', currency: category.currency_code}).format(category.spent) }}
</span>
</div>
<!-- EARNED -->
<div v-if="category.earnedPct > 0" class="progress justify-content-end" title="hello2">
<span v-if="category.earnedPct <= 20" style="line-height: 16px;">
{{ Intl.NumberFormat(locale, {style: 'currency', currency: category.currency_code}).format(category.earned) }}
&nbsp;</span>
<div :aria-valuenow="category.earnedPct" :style="{ width: category.earnedPct + '%'}" aria-valuemax="100"
aria-valuemin="0" class="progress-bar progress-bar-striped bg-success"
role="progressbar" title="hello">
<span v-if="category.earnedPct > 20">
{{ Intl.NumberFormat(locale, {style: 'currency', currency: category.currency_code}).format(category.earned) }}
</span>
</div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
<script>
import {createNamespacedHelpers} from "vuex";
import format from "date-fns/format";
const {mapState, mapGetters, mapActions, mapMutations} = createNamespacedHelpers('dashboard/index')
export default {
name: "MainCategoryList",
created() {
this.locale = localStorage.locale ?? 'en-US';
this.ready = true;
},
data() {
return {
locale: 'en-US',
categories: [],
sortedList: [],
spent: 0,
earned: 0,
loading: true,
error: false
}
},
computed: {
...mapGetters(['start', 'end']),
'datesReady': function () {
return null !== this.start && null !== this.end && this.ready;
}
},
watch: {
datesReady: function (value) {
if (true === value) {
this.getCategories();
}
},
start: function () {
if (false === this.loading) {
this.getCategories();
}
},
end: function () {
if (false === this.loading) {
this.getCategories();
}
},
},
methods:
{
getCategories: function () {
this.categories = [];
this.sortedList = [];
this.spent = 0;
this.earned = 0;
this.loading = true;
// let startStr = this.start.toISOString().split('T')[0];
// let endStr = this.end.toISOString().split('T')[0];
let startStr = format(this.start, 'y-MM-dd');
let endStr = format(this.end, 'y-MM-dd');
this.getCategoryPage(startStr, endStr, 1);
},
getCategoryPage: function (start, end, page) {
axios.get('./api/v1/categories?start=' + start + '&end=' + end + '&page=' + page)
.then(response => {
let categories = response.data.data;
let currentPage = parseInt(response.data.meta.pagination.current_page);
let totalPages = parseInt(response.data.meta.pagination.total_pages);
this.parseCategories(categories);
if (currentPage < totalPages) {
let nextPage = currentPage + 1;
this.getCategoryPage(start, end, nextPage);
}
if (currentPage === totalPages) {
this.loading = false;
this.sortCategories();
}
}
).catch(error => {
this.error = true;
});
},
parseCategories(data) {
for (let i in data) {
if (data.hasOwnProperty(i) && /^0$|^[1-9]\d*$/.test(i) && i <= 4294967294) {
let current = data[i];
let entryKey = null;
let categoryId = parseInt(current.id);
// loop spent info:
for (let ii in current.attributes.spent) {
if (current.attributes.spent.hasOwnProperty(ii) && /^0$|^[1-9]\d*$/.test(ii) && ii <= 4294967294) {
let spentData = current.attributes.spent[ii];
entryKey = spentData.currency_id + '-' + current.id;
// does the categories list thing have this combo? if not, create it.
this.categories[entryKey] = this.categories[entryKey] ??
{
id: categoryId,
name: current.attributes.name,
currency_code: spentData.currency_code,
currency_symbol: spentData.currency_symbol,
spent: 0,
earned: 0,
spentPct: 0,
earnedPct: 0,
};
this.categories[entryKey].spent = parseFloat(spentData.sum);
this.spent = parseFloat(spentData.sum) < this.spent ? parseFloat(spentData.sum) : this.spent;
}
}
// loop earned info
for (let ii in current.attributes.earned) {
if (current.attributes.earned.hasOwnProperty(ii) && /^0$|^[1-9]\d*$/.test(ii) && ii <= 4294967294) {
let earnedData = current.attributes.earned[ii];
entryKey = earnedData.currency_id + '-' + current.id;
// does the categories list thing have this combo? if not, create it.
this.categories[entryKey] = this.categories[entryKey] ??
{
id: categoryId,
name: current.attributes.name,
currency_code: earnedData.currency_code,
currency_symbol: earnedData.currency_symbol,
spent: 0,
earned: 0,
spentPct: 0,
earnedPct: 0,
};
this.categories[entryKey].earned = parseFloat(earnedData.sum);
this.earned = parseFloat(earnedData.sum) > this.earned ? parseFloat(earnedData.sum) : this.earned;
}
}
}
}
},
sortCategories() {
// no longer care about keys:
let array = [];
for (let i in this.categories) {
if (this.categories.hasOwnProperty(i)) {
array.push(this.categories[i]);
}
}
array.sort(function (one, two) {
return (one.spent + one.earned) - (two.spent + two.earned);
});
for (let i in array) {
if (array.hasOwnProperty(i)) {
let current = array[i];
current.spentPct = (current.spent / this.spent) * 100;
current.earnedPct = (current.earned / this.earned) * 100;
this.sortedList.push(current);
}
}
}
}
}
</script>

View File

@@ -1,169 +0,0 @@
<!--
- MainCreditList.vue
- Copyright (c) 2020 james@firefly-iii.org
-
- This file is part of Firefly III (https://github.com/firefly-iii).
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div class="card">
<div class="card-header">
<h3 class="card-title">{{ $t('firefly.revenue_accounts') }}</h3>
</div>
<!-- body if loading -->
<div v-if="loading && !error" class="card-body">
<div class="text-center">
<span class="fas fa-spinner fa-spin"></span>
</div>
</div>
<!-- body if error -->
<div v-if="error" class="card-body">
<div class="text-center">
<span class="fas fa-exclamation-triangle text-danger"></span>
</div>
</div>
<!-- body if normal -->
<div v-if="!loading && !error" class="card-body table-responsive p-0">
<table class="table table-sm">
<caption style="display:none;">{{ $t('firefly.revenue_accounts') }}</caption>
<thead>
<tr>
<th scope="col">{{ $t('firefly.account') }}</th>
<th scope="col">{{ $t('firefly.earned') }}</th>
</tr>
</thead>
<tbody>
<tr v-for="entry in income">
<td style="width:20%;"><a :href="'./accounts/show/' + entry.id">{{ entry.name }}</a></td>
<td class="align-middle">
<div v-if="entry.pct > 0" class="progress">
<div :aria-valuenow="entry.pct" :style="{ width: entry.pct + '%'}" aria-valuemax="100"
aria-valuemin="0" class="progress-bar bg-success"
role="progressbar">
<span v-if="entry.pct > 20">
{{ Intl.NumberFormat(locale, {style: 'currency', currency: entry.currency_code}).format(entry.difference_float) }}
</span>
</div>
<span v-if="entry.pct <= 20" style="line-height: 16px;">&nbsp;
{{ Intl.NumberFormat(locale, {style: 'currency', currency: entry.currency_code}).format(entry.difference_float) }}
</span>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<div class="card-footer">
<a class="btn btn-default button-sm" href="./transactions/deposit"><span class="far fa-money-bill-alt"></span> {{ $t('firefly.go_to_deposits') }}</a>
</div>
</div>
</template>
<script>
import {createNamespacedHelpers} from "vuex";
import format from "date-fns/format";
const {mapState, mapGetters, mapActions, mapMutations} = createNamespacedHelpers('dashboard/index')
// See reference nr. 2
export default {
name: "MainCreditList",
data() {
return {
locale: 'en-US',
income: [],
max: 0,
loading: true,
error: false
}
},
created() {
this.locale = localStorage.locale ?? 'en-US';
this.ready = true;
},
computed: {
...mapGetters([
'start',
'end'
]),
'datesReady': function () {
return null !== this.start && null !== this.end && this.ready;
}
},
watch: {
datesReady: function (value) {
if (true === value) {
this.getIncome();
}
},
start: function () {
if (false === this.loading) {
this.getIncome();
}
},
end: function () {
if (false === this.loading) {
this.getIncome();
}
},
},
methods: {
getIncome() {
this.loading = true;
this.income = [];
this.error = false;
// let startStr = this.start.toISOString().split('T')[0];
// let endStr = this.end.toISOString().split('T')[0];
let startStr = format(this.start, 'y-MM-dd');
let endStr = format(this.end, 'y-MM-dd');
axios.get('./api/v1/insight/income/revenue?start=' + startStr + '&end=' + endStr)
.then(response => {
// do something with response.
this.parseIncome(response.data);
this.loading = false;
}).catch(error => {
this.error = true
});
},
parseIncome(data) {
for (let i in data) {
if (data.hasOwnProperty(i)) {
let mainKey = parseInt(i);
// contains currency info and entries.
let current = data[mainKey];
current.pct = 0;
this.max = current.difference_float > this.max ? current.difference_float : this.max;
this.income.push(current);
}
}
if (0 === this.max) {
this.max = 1;
}
// now sort + pct:
for (let i in this.income) {
if (this.income.hasOwnProperty(i)) {
let current = this.income[i];
current.pct = (current.difference_float / this.max) * 100;
this.income[i] = current;
}
}
this.income.sort((a,b) => (a.pct > b.pct) ? -1 : ((b.pct > a.pct) ? 1 : 0));
}
}
}
</script>

View File

@@ -1,169 +0,0 @@
<!--
- MainDebitList.vue
- Copyright (c) 2020 james@firefly-iii.org
-
- This file is part of Firefly III (https://github.com/firefly-iii).
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div class="card">
<div class="card-header">
<h3 class="card-title">{{ $t('firefly.expense_accounts') }}</h3>
</div>
<!-- body if loading -->
<div v-if="loading && !error" class="card-body">
<div class="text-center">
<span class="fas fa-spinner fa-spin"></span>
</div>
</div>
<!-- body if error -->
<div v-if="error" class="card-body">
<div class="text-center">
<span class="fas fa-exclamation-triangle text-danger"></span>
</div>
</div>
<!-- body if normal -->
<div v-if="!loading && !error" class="card-body table-responsive p-0">
<table class="table table-sm">
<caption style="display:none;">{{ $t('firefly.expense_accounts') }}</caption>
<thead>
<tr>
<th scope="col">{{ $t('firefly.account') }}</th>
<th scope="col">{{ $t('firefly.spent') }}</th>
</tr>
</thead>
<tbody>
<tr v-for="entry in expenses">
<td style="width:20%;"><a :href="'./accounts/show/' + entry.id">{{ entry.name }}</a></td>
<td class="align-middle">
<div v-if="entry.pct > 0" class="progress">
<div :aria-valuenow="entry.pct" :style="{ width: entry.pct + '%'}" aria-valuemax="100"
aria-valuemin="0" class="progress-bar bg-danger"
role="progressbar">
<span v-if="entry.pct > 20">
{{ Intl.NumberFormat(locale, {style: 'currency', currency: entry.currency_code}).format(entry.difference_float) }}
</span>
</div>
<span v-if="entry.pct <= 20" style="line-height: 16px;">&nbsp;
{{ Intl.NumberFormat(locale, {style: 'currency', currency: entry.currency_code}).format(entry.difference_float) }}
</span>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<div class="card-footer">
<a class="btn btn-default button-sm" href="./transactions/withdrawal"><span class="far fa-money-bill-alt"></span> {{ $t('firefly.go_to_withdrawals') }}</a>
</div>
</div>
</template>
<script>
import {createNamespacedHelpers} from "vuex";
import format from "date-fns/format";
const {mapState, mapGetters, mapActions, mapMutations} = createNamespacedHelpers('dashboard/index')
export default {
name: "MainDebitList",
data() {
return {
locale: 'en-US',
expenses: [],
min: 0,
loading: true,
error: false
}
},
created() {
this.locale = localStorage.locale ?? 'en-US';
this.ready = true;
},
computed: {
...mapGetters([
'start',
'end'
]),
'datesReady': function () {
return null !== this.start && null !== this.end && this.ready;
}
},
watch: {
datesReady: function (value) {
if (true === value) {
this.getExpenses();
}
},
start: function () {
if (false === this.loading) {
this.getExpenses();
}
},
end: function () {
if (false === this.loading) {
this.getExpenses();
}
},
},
methods: {
getExpenses() {
this.loading = true;
this.error = false;
this.expenses = [];
// let startStr = this.start.toISOString().split('T')[0];
// let endStr = this.end.toISOString().split('T')[0];
let startStr = format(this.start, 'y-MM-dd');
let endStr = format(this.end, 'y-MM-dd');
axios.get('./api/v1/insight/expense/expense?start=' + startStr + '&end=' + endStr)
.then(response => {
// do something with response.
this.parseExpenses(response.data);
this.loading = false
}).catch(error => {
this.error = true
});
},
parseExpenses(data) {
for (let mainKey in data) {
if (data.hasOwnProperty(mainKey) && /^0$|^[1-9]\d*$/.test(mainKey) && mainKey <= 4294967294) {
let current = data[mainKey];
current.pct = 0;
this.min = current.difference_float < this.min ? current.difference_float : this.min;
this.expenses.push(current);
}
}
if (0 === this.min) {
this.min = -1;
}
// now sort + pct:
for (let i in this.expenses) {
if (this.expenses.hasOwnProperty(i)) {
let current = this.expenses[i];
current.pct = (current.difference_float*-1 / this.min*-1) * 100;
this.expenses[i] = current;
}
}
this.expenses.sort((a,b) => (a.pct > b.pct) ? -1 : ((b.pct > a.pct) ? 1 : 0));
}
}
}
</script>

View File

@@ -1,129 +0,0 @@
<!--
- MainPiggyList.vue
- Copyright (c) 2020 james@firefly-iii.org
-
- This file is part of Firefly III (https://github.com/firefly-iii).
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div class="card">
<div class="card-header">
<h3 class="card-title">{{ $t('firefly.piggy_banks') }}</h3>
</div>
<!-- body if loading -->
<div v-if="loading && !error" class="card-body">
<div class="text-center">
<span class="fas fa-spinner fa-spin"></span>
</div>
</div>
<!-- body if error -->
<div v-if="error" class="card-body">
<div class="text-center">
<span class="fas fa-exclamation-triangle text-danger"></span>
</div>
</div>
<!-- body if normal -->
<div v-if="!loading && !error" class="card-body table-responsive p-0">
<table class="table table-striped">
<caption style="display:none;">{{ $t('firefly.piggy_banks') }}</caption>
<thead>
<tr>
<th scope="col" style="width:35%;">{{ $t('list.piggy_bank') }}</th>
<th scope="col" style="width:40%;">{{ $t('list.percentage') }} <small>/ {{ $t('list.amount') }}</small></th>
</tr>
</thead>
<tbody>
<tr v-for="piggy in this.piggy_banks">
<td>
<a :href="'./piggy-banks/show/' + piggy.id" :title="piggy.attributes.name">{{ piggy.attributes.name }}</a>
<small v-if="piggy.attributes.object_group_title" class="text-muted">
<br/>
{{ piggy.attributes.object_group_title }}
</small>
</td>
<td>
<div class="progress-group">
<div class="progress progress-sm">
<div v-if="piggy.attributes.pct < 100" :style="{'width': piggy.attributes.pct + '%'}" class="progress-bar primary"></div>
<div v-if="100 === piggy.attributes.pct" :style="{'width': piggy.attributes.pct + '%'}"
class="progress-bar progress-bar-striped bg-success"></div>
</div>
</div>
<span class="text-success">
{{
Intl.NumberFormat(locale, {style: 'currency', currency: piggy.attributes.currency_code}).format(piggy.attributes.current_amount)
}}
</span>
of
<span class="text-success">{{
Intl.NumberFormat(locale, {
style: 'currency',
currency: piggy.attributes.currency_code
}).format(piggy.attributes.target_amount)
}}</span>
</td>
</tr>
</tbody>
</table>
</div>
<div class="card-footer">
<a class="btn btn-default button-sm" href="./piggy-banks"><span class="far fa-money-bill-alt"></span> {{ $t('firefly.go_to_piggies') }}</a>
</div>
</div>
</template>
<script>
export default {
name: "MainPiggyList",
data() {
return {
piggy_banks: [],
loading: true,
error: false,
locale: 'en-US'
}
},
created() {
this.locale = localStorage.locale ?? 'en-US';
axios.get('./api/v1/piggy_banks')
.then(response => {
this.loadPiggyBanks(response.data.data);
this.loading = false;
}
).catch(error => {
this.error = true
});
},
methods: {
loadPiggyBanks(data) {
for (let key in data) {
if (data.hasOwnProperty(key) && /^0$|^[1-9]\d*$/.test(key) && key <= 4294967294) {
let piggy = data[key];
if (0.0 !== parseFloat(piggy.attributes.left_to_save)) {
piggy.attributes.pct = (parseFloat(piggy.attributes.current_amount) / parseFloat(piggy.attributes.target_amount)) * 100;
this.piggy_banks.push(piggy);
}
}
}
this.piggy_banks.sort(function (a, b) {
return b.attributes.pct - a.attributes.pct;
});
}
}
}
</script>

View File

@@ -1,313 +0,0 @@
<!--
- TopBoxes.vue
- Copyright (c) 2020 james@firefly-iii.org
-
- This file is part of Firefly III (https://github.com/firefly-iii).
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div class="row">
<div class="col" v-if="0 !== prefCurrencyBalances.length || 0 !== notPrefCurrencyBalances.length">
<div class="info-box">
<span class="info-box-icon"><span class="far fa-bookmark text-info"></span></span>
<div class="info-box-content">
<span v-if="!loading && !error" class="info-box-text">{{ $t("firefly.balance") }}</span>
<span v-if="loading && !error" class="info-box-text"><span class="fas fa-spinner fa-spin"></span></span>
<span v-if="error" class="info-box-text"><span class="fas fa-exclamation-triangle text-danger"></span></span>
<!-- balance in preferred currency -->
<span v-for="balance in prefCurrencyBalances" :title="balance.sub_title" class="info-box-number">{{ balance.value_parsed }}</span>
<span v-if="0 === prefCurrencyBalances.length" class="info-box-number">&nbsp;</span>
<div class="progress bg-info">
<div class="progress-bar" style="width: 0"></div>
</div>
<!-- balance in not preferred currency -->
<span class="progress-description">
<span v-for="(balance, index) in notPrefCurrencyBalances" :title="balance.sub_title">
{{ balance.value_parsed }}<span v-if="index+1 !== notPrefCurrencyBalances.length">, </span>
</span>
<span v-if="0===notPrefCurrencyBalances.length">&nbsp;</span>
</span>
</div>
</div>
</div>
<div class="col" v-if="0!==prefBillsUnpaid.length || 0 !== notPrefBillsUnpaid.length">
<div class="info-box">
<span class="info-box-icon"><span class="far fa-calendar-alt text-teal"></span></span>
<div class="info-box-content">
<span v-if="!loading && !error" class="info-box-text">{{ $t('firefly.bills_to_pay') }}</span>
<span v-if="loading && !error" class="info-box-text"><span class="fas fa-spinner fa-spin"></span></span>
<span v-if="error" class="info-box-text"><span class="fas fa-exclamation-triangle text-danger"></span></span>
<!-- bills unpaid, in preferred currency. -->
<span v-for="balance in prefBillsUnpaid" class="info-box-number">{{ balance.value_parsed }}</span>
<div class="progress bg-teal">
<div class="progress-bar" style="width: 0"></div>
</div>
<!-- bills unpaid, in other currencies. -->
<span class="progress-description">
<span v-for="(bill, index) in notPrefBillsUnpaid">
{{ bill.value_parsed }}<span v-if="index+1 !== notPrefBillsUnpaid.length">, </span>
</span>
<span v-if="0===notPrefBillsUnpaid.length">&nbsp;</span>
</span>
</div>
</div>
</div>
<!-- left to spend -->
<div class="col" v-if="0 !== prefLeftToSpend.length || 0 !== notPrefLeftToSpend.length">
<div class="info-box">
<span class="info-box-icon"><span class="fas fa-money-bill text-success"></span></span>
<div class="info-box-content">
<span v-if="!loading && !error" class="info-box-text">{{ $t('firefly.left_to_spend') }}</span>
<span v-if="loading && !error" class="info-box-text"><span class="fas fa-spinner fa-spin"></span></span>
<span v-if="error" class="info-box-text"><span class="fas fa-exclamation-triangle text-danger"></span></span>
<!-- left to spend in preferred currency -->
<span v-for="left in prefLeftToSpend" :title="left.sub_title" class="info-box-number">{{ left.value_parsed }}</span>
<span v-if="0 === prefLeftToSpend.length" class="info-box-number">&nbsp;</span>
<div class="progress bg-success">
<div class="progress-bar" style="width: 0"></div>
</div>
<!-- other currencies-->
<span class="progress-description">
<span v-for="(left, index) in notPrefLeftToSpend">
{{ left.value_parsed }}<span v-if="index+1 !== notPrefLeftToSpend.length">, </span>
</span>
<span v-if="0===notPrefLeftToSpend.length">&nbsp;</span>
</span>
</div>
</div>
</div>
<!-- net worth -->
<div class="col" v-if="0 !== notPrefNetWorth.length || 0 !== prefNetWorth.length">
<div class="info-box">
<span class="info-box-icon"><span class="fas fa-money-bill text-success"></span></span>
<div class="info-box-content">
<span v-if="!loading && !error" class="info-box-text">{{ $t('firefly.net_worth') }}</span>
<span v-if="loading && !error" class="info-box-text"><span class="fas fa-spinner fa-spin"></span></span>
<span v-if="error" class="info-box-text"><span class="fas fa-exclamation-triangle text-danger"></span></span>
<span v-for="nw in prefNetWorth" :title="nw.sub_title" class="info-box-number">{{ nw.value_parsed }}</span>
<span v-if="0===prefNetWorth.length">&nbsp;</span>
<div class="progress bg-success">
<div class="progress-bar" style="width: 0"></div>
</div>
<span class="progress-description">
<span v-for="(nw, index) in notPrefNetWorth">
{{ nw.value_parsed }}<span v-if="index+1 !== notPrefNetWorth.length">, </span>
</span>
<span v-if="0===notPrefNetWorth.length">&nbsp;</span>
</span>
</div>
</div>
</div>
</div>
</template>
<script>
import {createNamespacedHelpers} from "vuex";
import format from 'date-fns/format';
const {mapState, mapGetters, mapActions, mapMutations} = createNamespacedHelpers('dashboard/index')
export default {
name: "TopBoxes",
props: {},
data() {
return {
summary: [],
balances: [],
billsPaid: [],
billsUnpaid: [],
leftToSpend: [],
netWorth: [],
loading: true,
error: false,
ready: false
}
},
computed: {
...mapGetters([
'start',
'end'
]),
'datesReady': function () {
return null !== this.start && null !== this.end && this.ready;
},
// contains only balances with preferred currency.
prefCurrencyBalances: function () {
return this.filterOnCurrency(this.balances);
},
notPrefCurrencyBalances: function () {
return this.filterOnNotCurrency(this.balances);
},
// contains only bills unpaid in preferred currency or first one.
prefBillsUnpaid: function () {
return this.filterOnCurrency(this.billsUnpaid);
},
notPrefBillsUnpaid: function () {
return this.filterOnNotCurrency(this.billsUnpaid);
},
// left to spend
prefLeftToSpend: function () {
return this.filterOnCurrency(this.leftToSpend);
},
notPrefLeftToSpend: function () {
return this.filterOnNotCurrency(this.leftToSpend);
},
// net worth
prefNetWorth: function () {
return this.filterOnCurrency(this.netWorth);
},
notPrefNetWorth: function () {
return this.filterOnNotCurrency(this.netWorth);
},
currencyCode() {
return this.$store.getters.currencyCode;
},
currencyId() {
return this.$store.getters.currencyId;
}
},
watch: {
datesReady: function (value) {
if (true === value) {
this.prepareComponent();
}
},
start: function () {
if (false === this.loading) {
this.prepareComponent();
}
},
end: function () {
if (false === this.loading) {
this.prepareComponent();
}
},
},
created() {
this.ready = true;
},
methods: {
filterOnCurrency(array) {
let ret = [];
for (const key in array) {
if (array.hasOwnProperty(key)) {
// console.log('Currency ID seems to be ' + this.currencyId);
if (array[key].currency_id === this.currencyId) {
ret.push(array[key]);
}
}
}
// or just the first one:
if (0 === ret.length && array.hasOwnProperty(0)) {
ret.push(array[0]);
}
return ret;
},
filterOnNotCurrency(array) {
let ret = [];
for (const key in array) {
if (array.hasOwnProperty(key)) {
if (array[key].currency_id !== this.currencyId) {
ret.push(array[key]);
}
}
}
return ret;
},
/**
* Prepare the component.
*/
prepareComponent() {
this.error = false;
this.loading = true;
this.summary = [];
this.balances = [];
this.billsPaid = [];
this.billsUnpaid = [];
this.leftToSpend = [];
this.netWorth = [];
let startStr = format(this.start, 'y-MM-dd');
let endStr = format(this.end, 'y-MM-dd');
//let startStr = this.start.toISOString().split('T')[0];
//let endStr = this.end.toISOString().split('T')[0];
//console.log(startStr);
//console.log(endStr);
axios.get('./api/v1/summary/basic?start=' + startStr + '&end=' + endStr)
.then(response => {
this.summary = response.data;
this.buildComponent();
this.loading = false
}).catch(error => {
this.error = true
});
},
buildComponent() {
this.getBalanceEntries();
this.getBillsEntries();
this.getLeftToSpend();
this.getNetWorth();
},
hasCurrency: function (array) {
for (const key in array) {
if (array.hasOwnProperty(key)) {
if (array[key].currency_id === this.currencyId) {
return true;
}
}
}
return false;
},
getBalanceEntries() {
this.balances = this.getKeyedEntries('balance-in-');
},
getNetWorth() {
this.netWorth = this.getKeyedEntries('net-worth-in-');
},
getLeftToSpend() {
this.leftToSpend = this.getKeyedEntries('left-to-spend-in-');
},
getBillsEntries() {
this.billsPaid = this.getKeyedEntries('bills-paid-in-');
this.billsUnpaid = this.getKeyedEntries('bills-unpaid-in-');
},
getKeyedEntries(expected) {
let result = [];
for (const key in this.summary) {
if (this.summary.hasOwnProperty(key)) {
if (expected === key.substr(0, expected.length)) {
result.push(this.summary[key]);
}
}
}
return result;
}
}
}
</script>

View File

@@ -1,172 +0,0 @@
<!--
- GenericAttachments.vue
- 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/>.
-->
<template>
<div class="form-group">
<div class="text-xs d-none d-lg-block d-xl-block">
{{ title }}
</div>
<div class="input-group">
<input
:class="errors.length > 0 ? 'form-control is-invalid' : 'form-control'"
:placeholder="title"
:name="fieldName"
multiple
ref="att"
@change="selectedFile"
type="file"
:disabled=disabled
/>
<span class="input-group-btn">
<button
class="btn btn-default"
type="button"
v-on:click="clearAtt"><span class="far fa-trash-alt"></span></button>
</span>
</div>
<span v-if="errors.length > 0">
<span v-for="error in errors" class="text-danger small">{{ error }}<br/></span>
</span>
</div>
</template>
<script>
export default {
name: "GenericAttachments",
props: {
title: {
type: String,
default: ''
},
disabled: {
type: Boolean,
default: false
},
fieldName: {
type: String,
default: ''
},
errors: {
type: Array,
default: function () {
return [];
}
},
uploadTrigger: {
type: Boolean,
default: false
},
uploadObjectType: {
type: String,
default: ''
},
uploadObjectId: {
type: Number,
default: 0
}
},
data() {
return {
localValue: this.value,
uploaded: 0,
uploads: 0,
}
},
watch: {
uploadTrigger: function (value) {
if (true === value) {
// this.createAttachment().then(response => {
// this.uploadAttachment(response.data.data.id, new Blob([evt.target.result]));
// });
// new code
// console.log('start of new');
let files = this.$refs.att.files;
this.uploads = files.length;
// loop all files and create attachments.
for (let i in files) {
if (files.hasOwnProperty(i) && /^0$|^[1-9]\d*$/.test(i) && i <= 4294967294) {
// console.log('Now at file ' + (parseInt(i) + 1) + ' / ' + files.length);
// read file into file reader:
let current = files[i];
let fileReader = new FileReader();
let theParent = this; // dont ask me why i need to do this.
fileReader.onloadend = evt => {
if (evt.target.readyState === FileReader.DONE) {
// console.log('I am done reading file ' + (parseInt(i) + 1));
this.createAttachment(current.name).then(response => {
// console.log('Created attachment. Now upload (1)');
return theParent.uploadAttachment(response.data.data.id, new Blob([evt.target.result]));
}).then(theParent.countAttachment);
}
}
fileReader.readAsArrayBuffer(current);
}
}
if (0 === files.length) {
// console.log('No files to upload. Emit event!');
this.$emit('uploaded-attachments', this.transaction_journal_id);
}
// Promise.all(promises).then(response => {
// console.log('All files uploaded. Emit event!');
// this.$emit('uploaded-attachments', this.transaction_journal_id);
// });
// end new code
}
},
},
methods: {
countAttachment: function () {
this.uploaded++;
// console.log('Uploaded ' + this.uploaded + ' / ' + this.uploads);
if (this.uploaded >= this.uploads) {
// console.log('All files uploaded. Emit event for ' + this.uploadObjectId);
this.$emit('uploaded-attachments', this.uploadObjectId);
}
},
uploadAttachment: function (attachmentId, data) {
this.created++;
// console.log('Now in uploadAttachment()');
const uploadUri = './api/v1/attachments/' + attachmentId + '/upload';
return axios.post(uploadUri, data)
},
createAttachment: function (name) {
const uri = './api/v1/attachments';
const data = {
filename: name,
attachable_type: this.uploadObjectType,
attachable_id: this.uploadObjectId,
};
return axios.post(uri, data);
},
selectedFile: function () {
this.$emit('selected-attachments');
},
clearAtt: function () {
this.$refs.att.value = '';
this.$emit('selected-no-attachments');
},
}
}
</script>

View File

@@ -1,83 +0,0 @@
<!--
- GenericTextInput.vue
- 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/>.
-->
<template>
<div class="form-group">
<div class="text-xs d-none d-lg-block d-xl-block">
{{ title }}
</div>
<div class="input-group">
<div class="form-check">
<input class="form-check-input" :disabled=disabled type="checkbox" v-model="localValue" :id="fieldName">
<label class="form-check-label" :for="fieldName">
{{ description }}
</label>
</div>
</div>
<span v-if="errors.length > 0">
<span v-for="error in errors" class="text-danger small">{{ error }}<br/></span>
</span>
</div>
</template>
<script>
export default {
name: "GenericCheckbox",
props: {
title: {
type: String,
default: ''
},
description: {
type: String,
default: ''
},
value: {
type: Boolean,
default: false
},
fieldName: {
type: String,
default: ''
},
disabled: {
type: Boolean,
default: false
},
errors: {
type: Array,
default: function () {
return [];
}
},
},
data() {
return {
localValue: this.value
}
},
watch: {
localValue: function (value) {
this.$emit('set-field', {field: this.fieldName, value: value});
},
}
}
</script>

View File

@@ -1,120 +0,0 @@
<!--
- Currency.vue
- 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/>.
-->
<template>
<div class="form-group">
<div class="text-xs d-none d-lg-block d-xl-block">
{{ $t('form.currency_id') }}
</div>
<div class="input-group" v-if="loading">
<span class="fas fa-spinner fa-spin"></span>
</div>
<div class="input-group" v-if="!loading">
<select
ref="currency_id"
v-model="currency_id"
:class="errors.length > 0 ? 'form-control is-invalid' : 'form-control'"
:title="$t('form.currency_id')"
autocomplete="off"
:disabled=disabled
name="currency_id"
>
<option v-for="currency in this.currencyList" :label="currency.name" :value="currency.id" :selected="value === currency.id">{{ currency.name }}</option>
</select>
</div>
<span v-if="errors.length > 0">
<span v-for="error in errors" class="text-danger small">{{ error }}<br/></span>
</span>
</div>
</template>
<script>
export default {
name: "GenericCurrency",
props: {
value: 0,
errors: [],
disabled: {
type: Boolean,
default: false
},
},
data() {
return {
loading: true,
currency_id: this.value,
currencyList: []
}
},
methods: {
loadCurrencies: function () {
this.loadCurrencyPage(1);
},
loadCurrencyPage: function (page) {
axios.get('./api/v1/currencies?page=' + page)
.then(response => {
let totalPages = parseInt(response.data.meta.pagination.total_pages);
let currentPage = parseInt(response.data.meta.pagination.current_page);
let currencies = response.data.data;
for (let i in currencies) {
if (currencies.hasOwnProperty(i)) {
let current = currencies[i];
if (true === current.attributes.default && null === this.value) {
this.currency_id = parseInt(current.id);
}
if (false === current.attributes.enabled) {
continue;
}
let currency = {
id: parseInt(current.id),
name: current.attributes.name,
};
this.currencyList.push(currency);
}
}
if (currentPage < totalPages) {
this.loadCurrencyPage(currentPage++);
}
if (currentPage >= totalPages) {
this.loading = false;
}
}
);
}
},
watch: {
currency_id: function (value) {
this.$emit('set-field', {field: 'currency_id', value: value});
},
value: function(value) {
this.currency_id = value;
}
},
created() {
this.loadCurrencies();
if (typeof this.value === 'number' && 0 !== this.value) {
this.currency_id = tthis.value;
}
}
}
</script>

View File

@@ -1,116 +0,0 @@
<!--
- GenericTextInput.vue
- 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/>.
-->
<template>
<div class="form-group">
<div class="text-xs d-none d-lg-block d-xl-block">
{{ title }}
</div>
<vue-typeahead-bootstrap
v-model="localValue"
:data="groupTitles"
:inputClass="errors.length > 0 ? 'is-invalid' : ''"
:minMatchingChars="3"
:placeholder="title"
:serializer="item => item.title"
:showOnFocus=true
autofocus
inputName="description[]"
@input="lookupGroupTitle"
>
<template slot="append">
<div class="input-group-append">
<button class="btn btn-outline-secondary" tabindex="-1" type="button" v-on:click="clearGroupTitle"><span class="far fa-trash-alt"></span></button>
</div>
</template>
</vue-typeahead-bootstrap>
<span v-if="errors.length > 0">
<span v-for="error in errors" class="text-danger small">{{ error }}<br/></span>
</span>
</div>
</template>
import VueTypeaheadBootstrap from 'vue-typeahead-bootstrap';
import {debounce} from "lodash";
<script>
import VueTypeaheadBootstrap from "vue-typeahead-bootstrap";
import {debounce} from "lodash";
export default {
name: "GenericGroup",
components: {VueTypeaheadBootstrap},
props: {
title: {
type: String,
default: ''
},
description: {
type: String,
default: ''
},
value: {
type: String,
default: ''
},
fieldName: {
type: String,
default: ''
},
disabled: {
type: Boolean,
default: false
},
errors: {
type: Array,
default: function () {
return [];
}
},
},
methods: {
clearGroupTitle: function () {
this.localValue = '';
},
getACURL: function (query) {
// update autocomplete URL:
return document.getElementsByTagName('base')[0].href + 'api/v1/autocomplete/object-groups?query=' + query;
},
lookupGroupTitle: debounce(function () {
// update autocomplete URL:
axios.get(this.getACURL(this.value))
.then(response => {
this.groupTitles = response.data;
})
}, 300)
},
data() {
return {
localValue: this.value,
groupTitles: [],
}
},
watch: {
localValue: function (value) {
this.$emit('set-field', {field: this.fieldName, value: value});
},
}
}
</script>

View File

@@ -1,187 +0,0 @@
<!--
- GenericLocation.vue
- 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/>.
-->
<template>
<div class="form-group" v-if="enableExternalMap">
<div class="text-xs d-none d-lg-block d-xl-block">
{{ title }}
</div>
<div style="width:100%;height:300px;">
<LMap
ref="myMap"
:center="center"
:zoom="zoom" style="width:100%;height:300px;"
@ready="prepMap"
@update:zoom="zoomUpdated"
@update:center="centerUpdated"
@update:bounds="boundsUpdated"
>
<l-tile-layer :url="url"></l-tile-layer>
<l-marker :lat-lng="marker" :visible="hasMarker"></l-marker>
</LMap>
<span>
<button class="btn btn-default btn-xs" @click="clearLocation">{{ $t('firefly.clear_location') }}</button>
</span>
</div>
<p>&nbsp;</p>
<span v-if="errors.length > 0">
<span v-for="error in errors" class="text-danger small">{{ error }}<br/></span>
</span>
</div>
</template>
<script>
// If you need to reference 'L', such as in 'L.icon', then be sure to
// explicitly import 'leaflet' into your component
// import L from 'leaflet';
// OLD
// import {LMap, LMarker, LTileLayer} from 'vue2-leaflet';
// import 'leaflet/dist/leaflet.css';
//
// import L from 'leaflet';
//
// delete L.Icon.Default.prototype._getIconUrl;
//
// L.Icon.Default.mergeOptions({
// iconRetinaUrl: require('leaflet/dist/images/marker-icon-2x.png'),
// iconUrl: require('leaflet/dist/images/marker-icon.png'),
// shadowUrl: require('leaflet/dist/images/marker-shadow.png')
// });
import {LMap, LMarker, LTileLayer} from 'vue2-leaflet';
import 'leaflet/dist/leaflet.css';
export default {
name: "GenericLocation",
components: {LMap, LTileLayer, LMarker,},
props: {
title: {},
disabled: {
type: Boolean,
default: false
},
value: {
type: Object,
required: true,
default: function () {
return {
// some defaults here.
};
}
},
errors: {},
customFields: {},
},
data() {
return {
availableFields: this.customFields,
url: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
zoom: 3,
center: [0, 0],
bounds: null,
map: null,
enableExternalMap: false,
hasMarker: false,
marker: [0, 0],
}
},
created() {
// enable_external_map
this.verifyMapEnabled();
},
methods: {
verifyMapEnabled: function () {
axios.get('./api/v1/configuration/firefly.enable_external_map').then(response => {
this.enableExternalMap = response.data.data.value;
if (true === this.enableExternalMap) {
this.loadMap();
}
});
},
loadMap: function () {
if (null === this.value || typeof this.value === 'undefined' || 0 === Object.keys(this.value).length) {
axios.get('./api/v1/configuration/firefly.default_location').then(response => {
this.zoom = parseInt(response.data.data.value.zoom_level);
this.center =
[
parseFloat(response.data.data.value.latitude),
parseFloat(response.data.data.value.longitude),
]
;
});
return;
}
if (null !== this.value.zoom_level && null !== this.value.latitude && null !== this.value.longitude) {
this.zoom = this.value.zoom_level;
this.center = [
parseFloat(this.value.latitude),
parseFloat(this.value.longitude),
];
this.hasMarker = true;
}
},
prepMap: function () {
this.map = this.$refs.myMap.mapObject;
this.map.on('contextmenu', this.setObjectLocation);
this.map.on('zoomend', this.saveZoomLevel);
},
setObjectLocation: function (event) {
this.marker = [event.latlng.lat, event.latlng.lng];
this.hasMarker = true;
this.emitEvent();
},
saveZoomLevel: function () {
this.emitEvent();
},
clearLocation: function (e) {
e.preventDefault();
this.hasMarker = false;
this.emitEvent();
},
emitEvent() {
this.$emit('set-field', {
field: "location",
value: {
zoomLevel: this.zoom,
lat: this.marker[0],
lng: this.marker[1],
hasMarker: this.hasMarker
}
}
);
},
zoomUpdated(zoom) {
this.zoom = zoom;
},
centerUpdated(center) {
this.center = center;
},
boundsUpdated(bounds) {
this.bounds = bounds;
}
},
}
</script>

View File

@@ -1,102 +0,0 @@
<!--
- GenericTextInput.vue
- 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/>.
-->
<template>
<div class="form-group">
<div class="text-xs d-none d-lg-block d-xl-block">
{{ title }}
</div>
<div class="input-group">
<input
v-model="localValue"
:class="errors.length > 0 ? 'form-control is-invalid' : 'form-control'"
:placeholder="title"
:name="fieldName"
ref="textInput"
:type="fieldType"
:disabled=disabled
:step="fieldStep"
/>
<div class="input-group-append">
<button class="btn btn-outline-secondary" v-on:click="clearText" tabindex="-1" type="button"><span class="far fa-trash-alt"></span></button>
</div>
</div>
<span v-if="errors.length > 0">
<span v-for="error in errors" class="text-danger small">{{ error }}<br/></span>
</span>
</div>
</template>
<script>
export default {
name: "GenericTextInput",
props: {
title: {
type: String,
default: ''
},
disabled: {
type: Boolean,
default: false
},
value: {
type: String,
default: ''
},
fieldName: {
type: String,
default: ''
},
fieldType: {
type: String,
default: 'text'
},
fieldStep: {
type: String,
default: ''
},
errors: {
type: Array,
default: function () {
return [];
}
},
},
data() {
return {
localValue: this.value
}
},
methods: {
clearText: function () {
this.localValue = '';
},
},
watch: {
localValue: function (value) {
this.$emit('set-field', {field: this.fieldName, value: value});
},
value: function(value) {
this.localValue = value;
}
}
}
</script>

View File

@@ -1,83 +0,0 @@
<!--
- GenericTextarea.vue
- 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/>.
-->
<template>
<div class="form-group">
<div class="text-xs d-none d-lg-block d-xl-block">
{{ title }}
</div>
<div class="input-group">
<textarea
v-model="localValue"
:class="errors.length > 0 ? 'form-control is-invalid' : 'form-control'"
:placeholder="title"
:disabled=disabled
:name="fieldName"
>{{ localValue }}</textarea>
</div>
<span v-if="errors.length > 0">
<span v-for="error in errors" class="text-danger small">{{ error }}<br/></span>
</span>
</div>
</template>
<script>
export default {
name: "GenericTextarea",
props: {
title: {
type: String,
default: ''
},
disabled: {
type: Boolean,
default: false
},
value: {
type: String,
default: ''
},
fieldName: {
type: String,
default: ''
},
errors: {
type: Array,
default: function () {
return [];
}
},
},
data() {
return {
localValue: this.value
}
},
watch: {
localValue: function (value) {
this.$emit('set-field', {field: this.fieldName, value: value});
},
value: function(value) {
this.localValue = value;
}
}
}
</script>

View File

@@ -1,110 +0,0 @@
<!--
- Index.vue
- Copyright (c) 2020 james@firefly-iii.org
-
- This file is part of Firefly III (https://github.com/firefly-iii).
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div class="row">
<div class="col">
<div id="accordion">
<!-- we are adding the .class so bootstrap.js collapse plugin detects it -->
<div class="card card-primary">
<div class="card-header">
<h4 class="card-title">
<a data-parent="#accordion" data-toggle="collapse" href="#collapseOne">
Create new accounts
</a>
</h4>
</div>
<div id="collapseOne" class="panel-collapse collapse show">
<div class="card-body">
<div class="row">
<div class="col">
<p>Explain</p>
</div>
</div>
<div class="row">
<div class="col-lg-4">
A
</div>
<div class="col-lg-8">
B
</div>
</div>
</div>
</div>
</div>
<div class="card card-secondary">
<div class="card-header">
<h4 class="card-title">
<a data-parent="#accordion" data-toggle="collapse" href="#collapseTwo">
Collapsible Group Danger
</a>
</h4>
</div>
<div id="collapseTwo" class="panel-collapse collapse">
<div class="card-body">
Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid.
3
wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt
laborum
eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee
nulla
assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred
nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft
beer
farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus
labore sustainable VHS.
</div>
</div>
</div>
<div class="card card-secondary">
<div class="card-header">
<h4 class="card-title">
<a data-parent="#accordion" data-toggle="collapse" href="#collapseThree">
Collapsible Group Success
</a>
</h4>
</div>
<div id="collapseThree" class="panel-collapse collapse">
<div class="card-body">
Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid.
3
wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt
laborum
eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee
nulla
assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred
nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft
beer
farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus
labore sustainable VHS.
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "Index"
}
</script>

View File

@@ -1,37 +0,0 @@
<!--
- Alert.vue
- 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/>.
-->
<template>
<div v-if="message.length > 0" :class="'alert alert-' + type + ' alert-dismissible'">
<button aria-hidden="true" class="close" data-dismiss="alert" type="button">×</button>
<h5>
<span v-if="'danger' === type" class="icon fas fa-ban"></span>
<span v-if="'success' === type" class="icon fas fa-thumbs-up"></span>
<span v-if="'danger' === type">{{ $t("firefly.flash_error") }}</span>
<span v-if="'success' === type">{{ $t("firefly.flash_success") }}</span>
</h5>
<span v-html="message"></span>
</div>
</template>
<script>
export default {
name: "Alert",
props: ['message', 'type']
}
</script>

View File

@@ -1,136 +0,0 @@
/*
* index.js
* Copyright (c) 2020 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import Vue from 'vue'
import Vuex, {createLogger} from 'vuex'
import transactions_create from './modules/transactions/create';
import transactions_edit from './modules/transactions/edit';
import dashboard_index from './modules/dashboard/index';
import root_store from './modules/root';
import accounts_index from './modules/accounts/index';
Vue.use(Vuex)
const debug = process.env.NODE_ENV !== 'production'
export default new Vuex.Store(
{
namespaced: true,
modules: {
root: root_store,
transactions: {
namespaced: true,
modules: {
create: transactions_create,
edit: transactions_edit
}
},
accounts: {
namespaced: true,
modules: {
index: accounts_index
},
},
dashboard: {
namespaced: true,
modules: {
index: dashboard_index
}
}
},
strict: debug,
plugins: debug ? [createLogger()] : [],
state: {
currencyPreference: {},
locale: 'en-US',
listPageSize: 50
},
mutations: {
setCurrencyPreference(state, payload) {
state.currencyPreference = payload.payload;
},
initialiseStore(state) {
// console.log('Now in initialiseStore()')
// if locale in local storage:
if (localStorage.locale) {
state.locale = localStorage.locale;
return;
}
// set locale from HTML:
let localeToken = document.head.querySelector('meta[name="locale"]');
if (localeToken) {
state.locale = localeToken.content;
localStorage.locale = localeToken.content;
}
}
},
getters: {
currencyCode: state => {
return state.currencyPreference.code;
},
currencyPreference: state => {
return state.currencyPreference;
},
currencyId: state => {
return state.currencyPreference.id;
},
locale: state => {
return state.locale;
}
},
actions: {
updateCurrencyPreference(context) {
// console.log('Now in updateCurrencyPreference');
if (localStorage.currencyPreference) {
context.commit('setCurrencyPreference', {payload: JSON.parse(localStorage.currencyPreference)});
return;
}
axios.get('./api/v1/currencies/default')
.then(response => {
let currencyResponse = {
id: parseInt(response.data.data.id),
name: response.data.data.attributes.name,
symbol: response.data.data.attributes.symbol,
code: response.data.data.attributes.code,
decimal_places: parseInt(response.data.data.attributes.decimal_places),
};
localStorage.currencyPreference = JSON.stringify(currencyResponse);
//console.log('getCurrencyPreference from server')
//console.log(JSON.stringify(currencyResponse));
context.commit('setCurrencyPreference', {payload: currencyResponse});
}).catch(err => {
// console.log('Got error response.');
console.error(err);
context.commit('setCurrencyPreference', {
payload: {
id: 1,
name: 'Euro',
symbol: '€',
code: 'EUR',
decimal_places: 2
}
});
});
}
}
}
);

View File

@@ -1,59 +0,0 @@
/*
* index.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/>.
*/
// initial state
const state = () => (
{
orderMode: false,
activeFilter: 1
}
)
// getters
const getters = {
orderMode: state => {
return state.orderMode;
},
activeFilter: state => {
return state.activeFilter;
}
}
// actions
const actions = {}
// mutations
const mutations = {
setOrderMode(state, payload) {
state.orderMode = payload;
},
setActiveFilter(state, payload) {
state.activeFilter = payload;
}
}
export default {
namespaced: true,
state,
getters,
actions,
mutations
}

View File

@@ -1,222 +0,0 @@
/*
* index.js
* Copyright (c) 2020 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
// initial state
import startOfDay from "date-fns/startOfDay";
import endOfDay from 'date-fns/endOfDay'
import startOfWeek from 'date-fns/startOfWeek'
import endOfWeek from 'date-fns/endOfWeek'
import startOfQuarter from 'date-fns/startOfQuarter';
import endOfQuarter from 'date-fns/endOfQuarter';
import endOfMonth from "date-fns/endOfMonth";
import startOfMonth from 'date-fns/startOfMonth';
const state = () => (
{
viewRange: 'default',
start: null,
end: null,
defaultStart: null,
defaultEnd: null,
}
)
// getters
const getters = {
start: state => {
return state.start;
},
end: state => {
return state.end;
},
defaultStart: state => {
return state.defaultStart;
},
defaultEnd: state => {
return state.defaultEnd;
},
viewRange: state => {
return state.viewRange;
}
}
// actions
const actions = {
initialiseStore(context) {
// console.log('initialiseStore for dashboard.');
// restore from local storage:
context.dispatch('restoreViewRange');
axios.get('./api/v1/preferences/viewRange')
.then(response => {
let viewRange = response.data.data.attributes.data;
let oldViewRange = context.getters.viewRange;
context.commit('setViewRange', viewRange);
if (viewRange !== oldViewRange) {
// console.log('View range changed from "' + oldViewRange + '" to "' + viewRange + '"');
context.dispatch('setDatesFromViewRange');
}
if (viewRange === oldViewRange) {
// console.log('Restore view range dates');
context.dispatch('restoreViewRangeDates');
}
}
).catch(() => {
context.commit('setViewRange', '1M');
context.dispatch('setDatesFromViewRange');
});
},
restoreViewRangeDates: function (context) {
// check local storage first?
if (localStorage.viewRangeStart) {
// console.log('view range start set from local storage.');
context.commit('setStart', new Date(localStorage.viewRangeStart));
}
if (localStorage.viewRangeEnd) {
// console.log('view range end set from local storage.');
context.commit('setEnd', new Date(localStorage.viewRangeEnd));
}
// also set default:
if (localStorage.viewRangeDefaultStart) {
// console.log('view range default start set from local storage.');
// console.log(localStorage.viewRangeDefaultStart);
context.commit('setDefaultStart', new Date(localStorage.viewRangeDefaultStart));
}
if (localStorage.viewRangeDefaultEnd) {
// console.log('view range default end set from local storage.');
// console.log(localStorage.viewRangeDefaultEnd);
context.commit('setDefaultEnd', new Date(localStorage.viewRangeDefaultEnd));
}
},
restoreViewRange: function (context) {
// console.log('restoreViewRange');
let viewRange = localStorage.getItem('viewRange');
if (null !== viewRange) {
// console.log('restored restoreViewRange ' + viewRange );
context.commit('setViewRange', viewRange);
}
},
setDatesFromViewRange(context) {
let start;
let end;
let viewRange = context.getters.viewRange;
let today = new Date;
// console.log('Will recreate view range on ' + viewRange);
switch (viewRange) {
case '1D':
// today:
start = startOfDay(today);
end = endOfDay(today);
break;
case '1W':
// this week:
start = startOfDay(startOfWeek(today, {weekStartsOn: 1}));
end = endOfDay(endOfWeek(today, {weekStartsOn: 1}));
break;
case '1M':
// this month:
start = startOfDay(startOfMonth(today));
end = endOfDay(endOfMonth(today));
break;
case '3M':
// this quarter
start = startOfDay(startOfQuarter(today));
end = endOfDay(endOfQuarter(today));
break;
case '6M':
// this half-year
if (today.getMonth() <= 5) {
start = new Date(today);
start.setMonth(0);
start.setDate(1);
start = startOfDay(start);
end = new Date(today);
end.setMonth(5);
end.setDate(30);
end = endOfDay(start);
}
if (today.getMonth() > 5) {
start = new Date(today);
start.setMonth(6);
start.setDate(1);
start = startOfDay(start);
end = new Date(today);
end.setMonth(11);
end.setDate(31);
end = endOfDay(start);
}
break;
case '1Y':
// this year
start = new Date(today);
start.setMonth(0);
start.setDate(1);
start = startOfDay(start);
end = new Date(today);
end.setMonth(11);
end.setDate(31);
end = endOfDay(end);
break;
}
// console.log('Range is ' + viewRange);
// console.log('Start is ' + start);
// console.log('End is ' + end);
context.commit('setStart', start);
context.commit('setEnd', end);
context.commit('setDefaultStart', start);
context.commit('setDefaultEnd', end);
}
}
// mutations
const mutations = {
setStart(state, value) {
state.start = value;
window.localStorage.setItem('viewRangeStart', value);
},
setEnd(state, value) {
state.end = value;
window.localStorage.setItem('viewRangeEnd', value);
},
setDefaultStart(state, value) {
state.defaultStart = value;
window.localStorage.setItem('viewRangeDefaultStart', value);
},
setDefaultEnd(state, value) {
state.defaultEnd = value;
window.localStorage.setItem('viewRangeDefaultEnd', value);
},
setViewRange(state, range) {
state.viewRange = range;
window.localStorage.setItem('viewRange', range);
}
}
export default {
namespaced: true,
state,
getters,
actions,
mutations
}

View File

@@ -1,138 +0,0 @@
/*
* root.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/>.
*/
// initial state
const state = () => (
{
listPageSize: 33,
timezone: '',
cacheKey: {
age: 0,
value: 'empty',
},
}
)
// getters
const getters = {
listPageSize: state => {
return state.listPageSize;
},
timezone: state => {
// console.log('Wil return ' + state.listPageSize);
return state.timezone;
},
cacheKey: state => {
return state.cacheKey.value;
},
}
// actions
const actions = {
initialiseStore(context) {
// cache key auto refreshes every day
// console.log('Now in initialize store.')
if (localStorage.cacheKey) {
// console.log('Storage has cache key: ');
// console.log(localStorage.cacheKey);
let object = JSON.parse(localStorage.cacheKey);
if (Date.now() - object.age > 86400000) {
// console.log('Key is here but is old.');
context.commit('refreshCacheKey');
} else {
// console.log('Cache key from local storage: ' + object.value);
context.commit('setCacheKey', object);
}
} else {
// console.log('No key need new one.');
context.commit('refreshCacheKey');
}
if (localStorage.listPageSize) {
state.listPageSize = localStorage.listPageSize;
context.commit('setListPageSize', {length: localStorage.listPageSize});
}
if (!localStorage.listPageSize) {
axios.get('./api/v1/preferences/listPageSize')
.then(response => {
// console.log('from API: listPageSize is ' + parseInt(response.data.data.attributes.data));
context.commit('setListPageSize', {length: parseInt(response.data.data.attributes.data)});
}
);
}
if (localStorage.timezone) {
state.timezone = localStorage.timezone;
context.commit('setTimezone', {timezone: localStorage.timezone});
}
if (!localStorage.timezone) {
axios.get('./api/v1/configuration/app.timezone')
.then(response => {
context.commit('setTimezone', {timezone: response.data.data.value});
}
);
}
}
}
// mutations
const mutations = {
refreshCacheKey(state) {
let age = Date.now();
let N = 8;
let cacheKey = Array(N+1).join((Math.random().toString(36)+'00000000000000000').slice(2, 18)).slice(0, N);
let object = {age: age, value: cacheKey};
// console.log('Store new key in string JSON');
// console.log(JSON.stringify(object));
localStorage.cacheKey = JSON.stringify(object);
state.cacheKey = {age: age, value: cacheKey};
// console.log('Refresh: cachekey is now ' + cacheKey);
},
setCacheKey(state, payload) {
// console.log('Stored cache key in localstorage.');
// console.log(payload);
// console.log(JSON.stringify(payload));
localStorage.cacheKey = JSON.stringify(payload);
state.cacheKey = payload;
},
setListPageSize(state, payload) {
// console.log('Got a payload in setListPageSize');
// console.log(payload);
let number = parseInt(payload.length);
if (0 !== number) {
state.listPageSize = number;
localStorage.listPageSize = number;
}
},
setTimezone(state, payload) {
if ('' !== payload.timezone) {
state.timezone = payload.timezone;
localStorage.timezone = payload.timezone;
}
},
}
export default {
namespaced: true,
state,
getters,
actions,
mutations
}

View File

@@ -1,153 +0,0 @@
/*
* create.js
* Copyright (c) 2020 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const lodashClonedeep = require('lodash.clonedeep');
import {getDefaultErrors, getDefaultTransaction} from '../../../../shared/transactions';
// initial state
const state = () => ({
transactionType: 'any',
groupTitle: '',
transactions: [],
customDateFields: {
interest_date: false,
book_date: false,
process_date: false,
due_date: false,
payment_date: false,
invoice_date: false,
},
defaultTransaction: getDefaultTransaction(),
defaultErrors: getDefaultErrors()
}
)
// getters
const getters = {
transactions: state => {
return state.transactions;
},
defaultErrors: state => {
return state.defaultErrors;
},
groupTitle: state => {
return state.groupTitle;
},
transactionType: state => {
return state.transactionType;
},
accountToTransaction: state => {
// See reference nr. 1
// possible API point!!
return state.accountToTransaction;
},
defaultTransaction: state => {
return state.defaultTransaction;
},
sourceAllowedTypes: state => {
return state.sourceAllowedTypes;
},
destinationAllowedTypes: state => {
return state.destinationAllowedTypes;
},
allowedOpposingTypes: state => {
return state.allowedOpposingTypes;
},
customDateFields: state => {
return state.customDateFields;
}
// // `getters` is localized to this module's getters
// // you can use rootGetters via 4th argument of getters
// someGetter (state, getters, rootState, rootGetters) {
// getters.someOtherGetter // -> 'foo/someOtherGetter'
// rootGetters.someOtherGetter // -> 'someOtherGetter'
// rootGetters['bar/someOtherGetter'] // -> 'bar/someOtherGetter'
// },
}
// actions
const actions = {}
// mutations
const mutations = {
addTransaction(state) {
let newTransaction = lodashClonedeep(state.defaultTransaction);
newTransaction.errors = lodashClonedeep(state.defaultErrors);
state.transactions.push(newTransaction);
},
resetErrors(state, payload) {
//console.log('resetErrors for index ' + payload.index);
state.transactions[payload.index].errors = lodashClonedeep(state.defaultErrors);
},
resetTransactions(state) {
// console.log('Store: Record call to resetTransactions :(');
state.transactions = [];
},
setGroupTitle(state, payload) {
state.groupTitle = payload.groupTitle;
},
setCustomDateFields(state, payload) {
state.customDateFields = payload;
},
deleteTransaction(state, payload) {
// console.log('Record call to deleteTransaction!');
state.transactions.splice(payload.index, 1);
// console.log('Deleted transaction ' + payload.index);
// console.log(state.transactions);
// if (0 === state.transactions.length) {
// console.log('array is empty!');
// }
},
setTransactionType(state, transactionType) {
state.transactionType = transactionType;
},
setAllowedOpposingTypes(state, allowedOpposingTypes) {
state.allowedOpposingTypes = allowedOpposingTypes;
},
setAccountToTransaction(state, payload) {
state.accountToTransaction = payload;
},
updateField(state, payload) {
state.transactions[payload.index][payload.field] = payload.value;
},
setTransactionError(state, payload) {
//console.log('Will set transactions[' + payload.index + '][errors][' + payload.field + '] to ');
//console.log(payload.errors);
state.transactions[payload.index].errors[payload.field] = payload.errors;
},
setDestinationAllowedTypes(state, payload) {
// console.log('Destination allowed types was changed!');
state.destinationAllowedTypes = payload;
},
setSourceAllowedTypes(state, payload) {
state.sourceAllowedTypes = payload;
}
}
export default {
namespaced: true,
state,
getters,
actions,
mutations
}

View File

@@ -1,40 +0,0 @@
/*
* edit.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/>.
*/
// initial state
const state = () => ({});
// getters
const getters = {};
// actions
const actions = {};
// mutations
const mutations = {};
export default {
namespaced: true,
state,
getters,
actions,
mutations
}

View File

@@ -1,905 +0,0 @@
<!--
- Create.vue
- Copyright (c) 2020 james@firefly-iii.org
-
- This file is part of Firefly III (https://github.com/firefly-iii).
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div>
<alert :message="errorMessage" type="danger"/>
<alert :message="successMessage" type="success"/>
<form @submit="submitTransaction" autocomplete="off">
<SplitPills ref="pills" :transactions="transactions" :count="transactions.length"/>
<div class="tab-content">
<SplitForm
v-for="(transaction, index) in this.transactions"
v-bind:key="index"
:count="transactions.length"
:custom-fields="customFields"
:date="date"
ref="splitForms"
:destination-allowed-types="destinationAllowedTypes"
:index="index"
:source-allowed-types="sourceAllowedTypes"
:submitted-transaction="submittedTransaction"
:transaction="transaction"
:transaction-type="transactionType"
v-on:uploaded-attachments="uploadedAttachment($event)"
v-on:selected-attachments="selectedAttachment($event)"
v-on:set-marker-location="storeLocation($event)"
v-on:set-account="storeAccountValue($event)"
v-on:set-date="storeDate($event)"
v-on:set-field="storeField($event)"
v-on:remove-transaction="removeTransaction($event)"
/>
</div>
<div class="row">
<!-- group title -->
<div class="col-xl-6 col-lg-6 col-md-12 col-sm-12 col-xs-12">
<div v-if="transactions.length > 1" class="card">
<div class="card-body">
<div class="row">
<div class="col">
<TransactionGroupTitle v-model="this.groupTitle" :errors="this.groupTitleErrors" v-on:set-group-title="storeGroupTitle($event)"/>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-6 col-lg-6 col-md-12 col-sm-12 col-xs-12">
<!-- buttons -->
<div class="card card-primary">
<div class="card-body">
<div class="row">
<div class="col">
<div class="text-xs d-none d-lg-block d-xl-block">
&nbsp;
</div>
<button type="button" class="btn btn-outline-primary btn-block" @click="addTransactionArray"><span class="far fa-clone"></span> {{
$t('firefly.add_another_split')
}}
</button>
</div>
<div class="col">
<div class="text-xs d-none d-lg-block d-xl-block">
&nbsp;
</div>
<button :disabled="!enableSubmit" class="btn btn-success btn-block" @click="submitTransaction">
<span v-if="enableSubmit"><span class="far fa-save"></span> {{ $t('firefly.store_transaction') }}</span>
<span v-if="!enableSubmit"><span class="fas fa-spinner fa-spin"></span></span>
</button>
</div>
</div>
<div class="row">
<div class="col">
&nbsp;
</div>
<div class="col">
<div class="form-check">
<input id="createAnother" v-model="createAnother" class="form-check-input" type="checkbox">
<label class="form-check-label" for="createAnother">
<span class="small">{{ $t('firefly.create_another') }}</span>
</label>
</div>
<div class="form-check">
<input id="resetFormAfter" v-model="resetFormAfter" :disabled="!createAnother" class="form-check-input" type="checkbox">
<label class="form-check-label" for="resetFormAfter">
<span class="small">{{ $t('firefly.reset_after') }}</span>
</label>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</form>
</div>
</template>
<script>
import Alert from '../partials/Alert';
import SplitPills from "./SplitPills";
import TransactionGroupTitle from "./TransactionGroupTitle";
import SplitForm from "./SplitForm";
import {mapGetters, mapMutations} from "vuex";
export default {
name: "Create",
components: {
SplitForm,
Alert,
SplitPills,
TransactionGroupTitle,
},
/**
* Grab some stuff from the API, add the first transaction.
*/
created() {
// set transaction type:
let pathName = window.location.pathname;
let parts = pathName.split('/');
let type = parts[parts.length - 1];
// set a basic date-time string:
let date = new Date;
this.date = [date.getFullYear(), ('0' + (date.getMonth() + 1)).slice(-2), ('0' + date.getDate()).slice(-2)].join('-') + 'T00:00';
//console.log('Date is set to "' + this.date + '"');
this.setTransactionType(type[0].toUpperCase() + type.substring(1));
this.getExpectedSourceTypes();
this.getAccountToTransaction();
this.getCustomFields();
this.addTransaction();
},
data() {
return {
// error or success message
errorMessage: '',
successMessage: '',
// custom fields to show, useful for components:
customFields: {},
// states for the form (makes sense right)
enableSubmit: true,
createAnother: false,
resetFormAfter: false,
// things the process is done working on (3 phases):
submittedTransaction: false,
submittedLinks: false,
submittedAttachments: -1, // -1 (no attachments), 0 = uploading, 1 = uploaded
// transaction was actually submitted?
inError: false,
// number of uploaded attachments
// its an object because we count per transaction journal (which can have multiple attachments)
// and array doesn't work right.
submittedAttCount: {},
// errors in the group title:
groupTitleErrors: [],
// group ID + title once submitted:
returnedGroupId: 0,
returnedGroupTitle: '',
// meta data for accounts
accountToTransaction: {},
allowedOpposingTypes: {},
sourceAllowedTypes: ['Asset account', 'Loan', 'Debt', 'Mortgage', 'Revenue account'],
destinationAllowedTypes: ['Asset account', 'Loan', 'Debt', 'Mortgage', 'Expense account'],
// date not in the store because it was buggy
date: ''
}
},
computed: {
/**
* Grabbed from the store.
*/
...mapGetters('transactions/create', ['transactionType', 'transactions', 'groupTitle', 'defaultErrors']),
...mapGetters('root', ['listPageSize'])
},
watch: {
submittedAttachments: function () {
this.finaliseSubmission();
}
},
methods: {
/**
* Store related mutators used by this component.
*/
...mapMutations('transactions/create',
[
'setGroupTitle',
'addTransaction',
'deleteTransaction',
'setTransactionError',
'setTransactionType',
'resetErrors',
'updateField',
'resetTransactions',
]
),
addTransactionArray: function (event) {
// console.log('Record call to addTransactionArray');
event.preventDefault();
this.addTransaction();
},
/**
* Removes a split from the array.
*/
removeTransaction: function (payload) {
// console.log('Record call to removeTransaction');
// console.log('Triggered to remove transaction ' + payload.index);
window.$('#tab_split_' + (payload.index - 1)).click();
this.$store.commit('transactions/create/deleteTransaction', payload);
},
submitData: function (url, data) {
return axios.post(url, data);
},
handleSubmissionResponse: function (response) {
// console.log('In handleSubmissionResponse()');
// save some meta data:
this.returnedGroupId = parseInt(response.data.data.id);
this.returnedGroupTitle = null === response.data.data.attributes.group_title ? response.data.data.attributes.transactions[0].description : response.data.data.attributes.group_title;
let journals = [];
// save separate journal ID's (useful ahead in the process):
let result = response.data.data.attributes.transactions
for (let i in result) {
if (result.hasOwnProperty(i) && /^0$|^[1-9]\d*$/.test(i) && i <= 4294967294) {
journals.push(parseInt(result[i].transaction_journal_id));
}
}
return Promise.resolve({journals: journals});
},
submitLinks: function (response, submission) {
let promises = [];
// for
for (let i in response.journals) {
if (response.journals.hasOwnProperty(i) && /^0$|^[1-9]\d*$/.test(i) && i <= 4294967294) {
let journalId = response.journals[i];
let links = submission.transactions[i].links;
for (let ii in links) {
if (links.hasOwnProperty(ii) && /^0$|^[1-9]\d*$/.test(ii) && ii <= 4294967294) {
let currentLink = links[ii];
if (0 === currentLink.outward_id) {
currentLink.outward_id = journalId;
}
if (0 === currentLink.inward_id) {
currentLink.inward_id = journalId;
}
promises.push(axios.post('./api/v1/transaction_links', currentLink));
}
}
}
}
if (0 === promises.length) {
return Promise.resolve({response: 'from submitLinks'});
}
return Promise.all(promises);
},
submitAttachments: function (response, submission) {
let anyAttachments = false;
for (let i in response.journals) {
if (response.journals.hasOwnProperty(i) && /^0$|^[1-9]\d*$/.test(i) && i <= 4294967294) {
let journalId = response.journals[i];
let hasAttachments = submission.transactions[i].attachments;
// console.log('Decided that ' + journalId);
// console.log(hasAttachments);
if (hasAttachments) {
// console.log('upload!');
this.updateField({index: i, field: 'transaction_journal_id', value: journalId});
// set upload trigger?
this.updateField({index: i, field: 'uploadTrigger', value: true});
//this.transactions[i].uploadTrigger = true;
anyAttachments = true;
}
}
}
if (true === anyAttachments) {
this.submittedAttachments = 0;
}
return Promise.resolve({response: 'from submitAttachments'});
},
selectedAttachment: function (payload) {
this.updateField({index: payload.index, field: 'attachments', value: true});
},
finaliseSubmission: function () {
// console.log('finaliseSubmission');
if (0 === this.submittedAttachments) {
// console.log('submittedAttachments = ' + this.submittedAttachments);
return;
}
// console.log('In finaliseSubmission');
if (false === this.createAnother) {
window.location.href = (window.previousURL ?? '/') + '?transaction_group_id=' + this.returnedGroupId + '&message=created';
return;
}
//console.log('Is in error?');
//console.log(this.inError);
if (false === this.inError) {
// show message:
this.errorMessage = '';
this.successMessage = this.$t('firefly.transaction_stored_link', {ID: this.returnedGroupId, title: this.returnedGroupTitle});
}
// console.log('here we are');
// enable flags:
this.enableSubmit = true;
this.submittedTransaction = false;
this.submittedAttachments = -1;
// reset attachments + errors
if (!this.resetFormAfter) {
for (let i in this.transactions) {
if (this.transactions.hasOwnProperty(i) && /^0$|^[1-9]\d*$/.test(i) && i <= 4294967294) {
if (this.transactions.hasOwnProperty(i)) {
// console.log('Reset attachment #' + i);
this.updateField({index: i, field: 'transaction_journal_id', value: 0});
this.updateField({index: i, field: 'errors', value: this.defaultErrors})
}
}
}
}
// reset the form:
if (this.resetFormAfter) {
this.resetTransactions();
setTimeout(this.addTransaction, 50);
}
return Promise.resolve({response: 'from finaliseSubmission'});
},
handleSubmissionError: function (error) {
//console.log('in handleSubmissionError');
// oh noes Firefly III has something to bitch about.
this.enableSubmit = true;
// but report an error because error:
this.inError = true;
this.parseErrors(error.response.data);
},
/**
* Actually submit the transaction to Firefly III. This is a fairly complex beast of a thing because multiple things
* need to happen in the right order.
*/
submitTransaction: function (event) {
event.preventDefault();
// console.log('submitTransaction()');
// disable the submit button:
this.enableSubmit = false;
// assume nothing breaks
this.inError = false;
// remove old warnings etc.
this.successMessage = '';
this.errorMessage = '';
// convert the data so its ready to be submitted:
const url = './api/v1/transactions';
const data = this.convertData();
this.submitData(url, data)
.then(this.handleSubmissionResponse)
.then(response => {
return Promise.all([this.submitLinks(response, data), this.submitAttachments(response, data)]);
}
)
.then(this.finaliseSubmission)
.catch(this.handleSubmissionError);
},
/**
* When a attachment component is done uploading it ends up here. We create an object where we count how many
* attachment components have reported back they're done uploading. Of course if they have nothing to upload
* they will be pretty fast in reporting they're done.
*
* Once the number of components matches the number of splits we know all attachments have been uploaded.
*/
uploadedAttachment: function (journalId) {
this.submittedAttachments = 0;
// console.log('Triggered uploadedAttachment(' + journalId + ')');
let key = 'str' + journalId;
this.submittedAttCount[key] = 1;
let count = Object.keys(this.submittedAttCount).length;
// console.log('Count is now ' + count);
// console.log('Length is ' + this.transactions.length);
if (count === this.transactions.length) {
// console.log('Got them all!');
// mark the attachments as stored:
this.submittedAttachments = 1;
}
},
/**
* Responds to changed location.
*/
storeLocation: function (payload) {
let zoomLevel = payload.hasMarker ? payload.zoomLevel : null;
let lat = payload.hasMarker ? payload.lat : null;
let lng = payload.hasMarker ? payload.lng : null;
this.updateField({index: payload.index, field: 'zoom_level', value: zoomLevel});
this.updateField({index: payload.index, field: 'latitude', value: lat});
this.updateField({index: payload.index, field: 'longitude', value: lng});
},
/**
* Responds to changed account.
*/
storeAccountValue: function (payload) {
this.updateField({index: payload.index, field: payload.direction + '_account_id', value: payload.id});
this.updateField({index: payload.index, field: payload.direction + '_account_type', value: payload.type});
this.updateField({index: payload.index, field: payload.direction + '_account_name', value: payload.name});
this.updateField({index: payload.index, field: payload.direction + '_account_currency_id', value: payload.currency_id});
this.updateField({index: payload.index, field: payload.direction + '_account_currency_code', value: payload.currency_code});
this.updateField({index: payload.index, field: payload.direction + '_account_currency_symbol', value: payload.currency_symbol});
//this.calculateTransactionType(payload.index);
if ('source' === payload.direction && true === payload.user_selected) {
this.$refs.splitForms[payload.index].$refs.destinationAccount.giveFocus();
}
if ('destination' === payload.direction && true === payload.user_selected) {
this.$refs.splitForms[payload.index].$refs.amount.giveFocus();
}
},
storeField: function (payload) {
this.updateField(payload);
if('description' === payload.field) {
// jump to account
//this.$refs.splitForms[payload.index].$refs.sourceAccount.giveFocus();
}
},
storeDate: function (payload) {
this.date = payload.date;
},
storeGroupTitle: function (value) {
// console.log('set group title: ' + value);
this.setGroupTitle({groupTitle: value});
},
/**
* Submit transaction links.
*/
submitTransactionLinks(data, response) {
//console.log('submitTransactionLinks()');
let promises = [];
let result = response.data.data.attributes.transactions;
let total = 0;
for (let i in data.transactions) {
if (data.transactions.hasOwnProperty(i) && /^0$|^[1-9]\d*$/.test(i) && i <= 4294967294) {
let submitted = data.transactions[i];
if (result.hasOwnProperty(i)) {
// found matching created transaction.
let received = result[i];
// grab ID from received, loop "submitted" transaction links
for (let ii in submitted.links) {
if (submitted.links.hasOwnProperty(ii) && /^0$|^[1-9]\d*$/.test(ii) && ii <= 4294967294) {
let currentLink = submitted.links[ii];
total++;
if (0 === currentLink.outward_id) {
currentLink.outward_id = received.transaction_journal_id;
}
if (0 === currentLink.inward_id) {
currentLink.inward_id = received.transaction_journal_id;
}
// submit transaction link:
promises.push(axios.post('./api/v1/transaction_links', currentLink).then(response => {
// See reference nr. 4
}));
}
}
}
}
}
if (0 === total) {
this.submittedLinks = true;
return;
}
Promise.all(promises).then(function () {
this.submittedLinks = true;
});
},
parseErrors: function (errors) {
for (let i in this.transactions) {
if (this.transactions.hasOwnProperty(i)) {
this.resetErrors({index: i});
}
}
this.successMessage = '';
this.errorMessage = this.$t('firefly.errors_submission');
if (typeof errors.errors === 'undefined') {
this.successMessage = '';
this.errorMessage = errors.message;
}
let payload;
let transactionIndex;
let fieldName;
// fairly basic way of exploding the error array.
for (const key in errors.errors) {
// console.log('Error index: "' + key + '"');
if (errors.errors.hasOwnProperty(key)) {
if (key === 'group_title') {
this.groupTitleErrors = errors.errors[key];
continue;
}
if (key !== 'group_title') {
// lol dumbest way to explode "transactions.0.something" ever.
transactionIndex = parseInt(key.split('.')[1]);
fieldName = key.split('.')[2];
// set error in this object thing.
// console.log('The errors in key "' + key + '" are');
// console.log(errors.errors[key]);
switch (fieldName) {
case 'amount':
case 'description':
case 'date':
case 'tags':
payload = {index: transactionIndex, field: fieldName, errors: errors.errors[key]};
this.setTransactionError(payload);
break;
case 'budget_id':
payload = {index: transactionIndex, field: 'budget', errors: errors.errors[key]};
this.setTransactionError(payload);
break;
case 'bill_id':
payload = {index: transactionIndex, field: 'bill', errors: errors.errors[key]};
this.setTransactionError(payload);
break;
case 'piggy_bank_id':
payload = {index: transactionIndex, field: 'piggy_bank', errors: errors.errors[key]};
this.setTransactionError(payload);
break;
case 'category_name':
payload = {index: transactionIndex, field: 'category', errors: errors.errors[key]};
this.setTransactionError(payload);
break;
case 'source_name':
case 'source_id':
payload = {index: transactionIndex, field: 'source', errors: errors.errors[key]};
this.setTransactionError(payload);
break;
case 'destination_name':
case 'destination_id':
payload = {index: transactionIndex, field: 'destination', errors: errors.errors[key]};
this.setTransactionError(payload);
break;
case 'foreign_amount':
case 'foreign_currency':
payload = {index: transactionIndex, field: 'foreign_amount', errors: errors.errors[key]};
this.setTransactionError(payload);
break;
}
}
// unique some things
if (typeof this.transactions[transactionIndex] !== 'undefined') {
//this.transactions[transactionIndex].errors.source = Array.from(new Set(this.transactions[transactionIndex].errors.source));
//this.transactions[transactionIndex].errors.destination = Array.from(new Set(this.transactions[transactionIndex].errors.destination));
}
}
}
},
/**
*
*/
convertData: function () {
//console.log('now in convertData');
let data = {
'transactions': []
};
//console.log('Group title is: "' + this.groupTitle + '"');
if (this.groupTitle.length > 0) {
data.group_title = this.groupTitle;
//console.log('1) data.group_title is now "'+data.group_title+'"');
}
for (let i in this.transactions) {
if (this.transactions.hasOwnProperty(i) && /^0$|^[1-9]\d*$/.test(i) && i <= 4294967294) {
data.transactions.push(this.convertSplit(i, this.transactions[i]));
}
}
if (data.transactions.length > 1 && '' !== data.transactions[0].description && (null === data.group_title || '' === data.group_title)) {
data.group_title = data.transactions[0].description;
//console.log('2) data.group_title is now "'+data.group_title+'"');
}
// depending on the transaction type for this thing, we need to
// make sure other splits match the data we submit.
if (data.transactions.length > 1) {
// console.log('This is a split!');
data = this.synchronizeAccounts(data);
}
return data;
},
synchronizeAccounts: function (data) {
// console.log('synchronizeAccounts: ' + this.transactionType);
// make sure all splits have whatever is in split 0.
// since its a transfer we can drop the name and use ID's only.
for (let i in data.transactions) {
if (data.transactions.hasOwnProperty(i) && /^0$|^[1-9]\d*$/.test(i) && i <= 4294967294) {
// console.log('now at ' + i);
// for transfers, overrule both the source and the destination:
if ('transfer' === this.transactionType.toLowerCase()) {
data.transactions[i].source_name = null;
data.transactions[i].destination_name = null;
if (i > 0) {
data.transactions[i].source_id = data.transactions[0].source_id;
data.transactions[i].destination_id = data.transactions[0].destination_id;
}
}
// for deposits, overrule the destination and ignore the rest.
if ('deposit' === this.transactionType.toLowerCase()) {
data.transactions[i].destination_name = null;
if (i > 0) {
data.transactions[i].destination_id = data.transactions[0].destination_id;
}
}
// for withdrawals, overrule the source and ignore the rest.
if ('withdrawal' === this.transactionType.toLowerCase()) {
data.transactions[i].source_name = null;
if (i > 0) {
data.transactions[i].source_id = data.transactions[0].source_id;
}
}
}
}
return data;
},
/**
*
* @param key
* @param array
*/
convertSplit: function (key, array) {
if ('' === array.destination_account_name) {
array.destination_account_name = null;
}
if (0 === array.destination_account_id) {
array.destination_account_name = null;
}
if ('' === array.source_account_name) {
array.source_account_name = null;
}
if (0 === array.source_account_id) {
array.source_account_id = null;
}
let currentSplit = {
// basic
description: array.description,
date: this.date,
type: this.transactionType.toLowerCase(),
// account
source_id: array.source_account_id ?? null,
source_name: array.source_account_name ?? null,
destination_id: array.destination_account_id ?? null,
destination_name: array.destination_account_name ?? null,
// amount:
currency_id: array.currency_id,
amount: array.amount,
// meta data
budget_id: array.budget_id,
category_name: array.category,
// optional date fields (6x):
interest_date: array.interest_date,
book_date: array.book_date,
process_date: array.process_date,
due_date: array.due_date,
payment_date: array.payment_date,
invoice_date: array.invoice_date,
// other optional fields:
internal_reference: array.internal_reference,
external_url: array.external_url,
notes: array.notes,
external_id: array.external_id,
// location:
zoom_level: array.zoom_level,
longitude: array.longitude,
latitude: array.latitude,
tags: [],
// from thing:
order: 0,
reconciled: false,
attachments: array.attachments,
};
if (0 !== array.tags.length) {
for (let i in array.tags) {
if (array.tags.hasOwnProperty(i) && /^0$|^[1-9]\d*$/.test(i) && i <= 4294967294) {
// array.tags
let current = array.tags[i];
if (typeof current === 'object' && null !== current) {
currentSplit.tags.push(current.text);
// console.log('Add tag "' + current.text + '" from object.');
continue;
}
if (typeof current === 'string') {
currentSplit.tags.push(current);
// console.log('Add tag "' + current + '" from string.');
continue;
}
// console.log('Is neither.');
}
}
}
// console.log('Current split tags is now: ');
// console.log(currentSplit.tags);
// bills and piggy banks
if (0 !== array.piggy_bank_id) {
currentSplit.piggy_bank_id = array.piggy_bank_id;
}
if (0 !== array.bill_id) {
currentSplit.bill_id = array.bill_id;
}
// foreign amount:
if (0 !== array.foreign_currency_id && '' !== array.foreign_amount) {
currentSplit.foreign_currency_id = array.foreign_currency_id;
}
if ('' !== array.foreign_amount) {
currentSplit.foreign_amount = array.foreign_amount;
}
// do transaction type
// let transactionType;
// let firstSource;
// let firstDestination;
// get transaction type from first transaction
//transactionType = this.transactionType ? this.transactionType.toLowerCase() : 'any';
//console.log('Transaction type is now ' + transactionType);
// if the transaction type is invalid, might just be that we can deduce it from
// the presence of a source or destination account
//firstSource = this.transactions[0].source_account_type;
//firstDestination = this.transactions[0].destination_account_type;
//console.log(this.transactions[0].source_account);
//console.log(this.transactions[0].destination_account);
//console.log('Type of first source is ' + firstSource);
//console.log('Type of first destination is ' + firstDestination);
// default to source:
currentSplit.currency_id = array.source_account_currency_id;
// if ('any' === transactionType && ['asset', 'Asset account', 'Loan', 'Debt', 'Mortgage'].includes(firstSource)) {
// transactionType = 'withdrawal';
// }
if ('Deposit' === this.transactionType) {
// transactionType = 'deposit';
currentSplit.currency_id = array.destination_account_currency_id;
}
//currentSplit.type = transactionType;
//console.log('Final type is ' + transactionType);
let links = [];
for (let i in array.links) {
if (array.links.hasOwnProperty(i) && /^0$|^[1-9]\d*$/.test(i) && i <= 4294967294) {
let current = array.links[i];
let linkTypeParts = current.link_type_id.split('-');
let inwardId = 'outward' === linkTypeParts[1] ? 0 : parseInt(current.transaction_journal_id);
let outwardId = 'inward' === linkTypeParts[1] ? 0 : parseInt(current.transaction_journal_id);
let newLink = {
link_type_id: parseInt(linkTypeParts[0]),
inward_id: inwardId,
outward_id: outwardId,
};
links.push(newLink);
}
}
currentSplit.links = links;
if (null === currentSplit.source_id) {
delete currentSplit.source_id;
}
if (null === currentSplit.source_name) {
delete currentSplit.source_name;
}
if (null === currentSplit.destination_id) {
delete currentSplit.destination_id;
}
if (null === currentSplit.destination_name) {
delete currentSplit.destination_name;
}
// console.log('Current split is: ');
// console.log(currentSplit);
// return it.
return currentSplit;
},
/**
* Get API value.
*/
getAllowedOpposingTypes: function () {
axios.get('./api/v1/configuration/firefly.allowed_opposing_types')
.then(response => {
// console.log('opposing types things.');
// console.log(response.data.data.value);
this.allowedOpposingTypes = response.data.data.value;
});
},
getExpectedSourceTypes: function () {
axios.get('./api/v1/configuration/firefly.expected_source_types')
.then(response => {
//console.log('getExpectedSourceTypes.');
this.sourceAllowedTypes = response.data.data.value.source[this.transactionType];
this.destinationAllowedTypes = response.data.data.value.destination[this.transactionType];
// console.log('sourceAllowedTypes');
// console.log(this.sourceAllowedTypes);
// console.log('Source allowed types for ' + this.transactionType + ' is: ');
// console.log(this.sourceAllowedTypes);
// console.log('Destination allowed types for ' + this.transactionType + ' is: ');
// console.log(this.destinationAllowedTypes);
//this.allowedOpposingTypes = response.data.data.value;
});
},
/**
* Get API value.
*/
getAccountToTransaction: function () {
axios.get('./api/v1/configuration/firefly.account_to_transaction')
.then(response => {
this.accountToTransaction = response.data.data.value;
});
},
/**
* This method grabs the users preferred custom transaction fields. It's used when configuring the
* custom date selects that will be available. It could be something the component does by itself,
* thereby separating concerns. This is on my list. If it changes to a per-component thing, then
* it should be done via the create.js Vue store because multiple components are interested in the
* user's custom transaction fields.
*/
getCustomFields: function () {
axios.get('./api/v1/preferences/transaction_journal_optional_fields').then(response => {
this.customFields = response.data.data.attributes.data;
});
},
setDestinationAllowedTypes: function (value) {
// console.log('Create::setDestinationAllowedTypes');
// console.log(value);
if (0 === value.length) {
this.destinationAllowedTypes = this.defaultDestinationAllowedTypes;
//console.log('empty so back to defaults');
return;
}
this.destinationAllowedTypes = value;
},
setSourceAllowedTypes(value) {
// console.log('Create::setSourceAllowedTypes');
// console.log(value);
if (0 === value.length) {
this.sourceAllowedTypes = this.defaultSourceAllowedTypes;
// console.log('empty so back to defaults');
return;
}
this.sourceAllowedTypes = value;
}
},
}
</script>

File diff suppressed because it is too large Load Diff

View File

@@ -1,177 +0,0 @@
<!--
- Index.vue
- 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/>.
-->
<template>
<div>
<!-- charts here (see file history) -->
<!-- page is ignored for the time being -->
<TransactionListLarge
:entries="rawTransactions"
:page="currentPage"
:total="total"
:per-page="perPage"
:sort-desc="sortDesc"
v-on:jump-page="jumpToPage($event)"
v-on:refreshed-cache-key="refreshedKey"
/>
</div>
</template>
<script>
import {mapGetters, mapMutations} from "vuex";
import format from "date-fns/format";
import sub from "date-fns/sub";
import startOfMonth from "date-fns/startOfMonth";
import endOfMonth from "date-fns/endOfMonth";
import {configureAxios} from "../../shared/forageStore";
import TransactionListLarge from "./TransactionListLarge";
export default {
name: "Index",
components: {TransactionListLarge},
data() {
return {
rawTransactions: [],
type: 'all',
downloaded: false,
loading: false,
ready: false,
currentPage: 1,
perPage: 5,
total: 1,
sortBy: 'order',
sortDesc: false,
api: null,
sortableOptions: {
disabled: false,
chosenClass: 'is-selected',
onEnd: null
},
sortable: null,
locale: 'en-US',
ranges: [],
urlStart: null,
urlEnd: null,
}
},
watch: {
storeReady: function () {
this.getTransactionList();
},
start: function () {
this.getTransactionList();
},
end: function () {
this.getTransactionList();
},
activeFilter: function (value) {
this.filterAccountList();
}
},
computed: {
...mapGetters('root', ['listPageSize', 'cacheKey']),
...mapGetters('dashboard/index', ['start', 'end',]),
'indexReady': function () {
return null !== this.start && null !== this.end && null !== this.listPageSize && this.ready;
},
cardTitle: function () {
return this.$t('firefly.' + this.type + '_transactions');
}
},
created() {
this.locale = localStorage.locale ?? 'en-US';
let pathName = window.location.pathname;
let parts = pathName.split('/');
this.type = parts[parts.length - 1];
this.perPage = this.listPageSize ?? 51;
if (5 === parts.length) {
this.urlStart = new Date(parts[3]);
this.urlEnd = new Date(parts[4]);
this.type = parts[parts.length - 3];
}
let params = new URLSearchParams(window.location.search);
this.currentPage = params.get('page') ? parseInt(params.get('page')) : 1;
this.ready = true;
},
methods: {
...mapMutations('root', ['refreshCacheKey',]),
refreshedKey: function () {
this.downloaded = false;
this.rawTransactions = [];
this.getTransactionList();
},
jumpToPage: function (event) {
// console.log('noticed a change in transactions/index.vue!');
this.currentPage = event.page;
this.downloadTransactionList(event.page);
},
getTransactionList: function () {
if (this.indexReady && !this.loading && !this.downloaded) {
this.loading = true;
this.perPage = this.listPageSize ?? 51;
this.rawTransactions = [];
this.downloadTransactionList(this.currentPage);
this.calculateDateRanges();
}
},
calculateDateRanges: function () {
let yearAgo = sub(this.start, {years: 1});
let currentDate = this.start;
while (currentDate > yearAgo) {
let st = startOfMonth(currentDate);
let en = endOfMonth(currentDate);
this.ranges.push({start: st, end: en});
currentDate = sub(currentDate, {months: 1});
}
},
formatDate: function (date, frm) {
return format(date, frm);
},
downloadTransactionList: function (page) {
configureAxios().then(async (api) => {
let startStr = format(this.start, 'y-MM-dd');
let endStr = format(this.end, 'y-MM-dd');
if (null !== this.urlEnd && null !== this.urlStart) {
startStr = format(this.urlStart, 'y-MM-dd');
endStr = format(this.urlEnd, 'y-MM-dd');
}
let url = './api/v1/transactions?type=' + this.type + '&page=' + page + "&start=" + startStr + "&end=" + endStr + '&cache=' + this.cacheKey;
api.get(url)
.then(response => {
this.total = parseInt(response.data.meta.pagination.total);
this.rawTransactions = response.data.data;
this.loading = false;
}
);
});
},
},
}
</script>

View File

@@ -1,464 +0,0 @@
<!--
- SplitForm.vue
- 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/>.
-->
<template>
<div :id="'split_' + index" :class="'tab-pane' + (0 === index ? ' active' : '')">
<div class="row">
<div class="col">
<div class="card">
<div class="card-header">
<h3 class="card-title">
{{ $t('firefly.basic_journal_information') }}
<span v-if="count > 1">({{ index + 1 }} / {{ count }}) </span>
</h3>
<div v-if="count>1" class="card-tools">
<button type="button" class="btn btn-danger btn-xs" @click="removeTransaction"><span class="fas fa-trash-alt"></span></button>
</div>
</div>
<div class="card-body">
<!-- start of body -->
<div class="row">
<div class="col">
<TransactionDescription
v-model="transaction.description"
v-on="$listeners"
:errors="transaction.errors.description"
:index="index"
></TransactionDescription>
</div>
</div>
<!-- source and destination -->
<div class="row">
<div class="col-xl-5 col-lg-5 col-md-10 col-sm-12 col-xs-12">
<!-- SOURCE -->
<TransactionAccount
v-model="sourceAccount"
v-on="$listeners"
:destination-allowed-types="destinationAllowedTypes"
:errors="transaction.errors.source"
:index="index"
:source-allowed-types="sourceAllowedTypes"
:transaction-type="transactionType"
direction="source"
ref="sourceAccount"
v-on:selected-account="triggerNextAccount($event)"
/>
</div>
<!-- switcharoo! -->
<div class="col-xl-2 col-lg-2 col-md-2 col-sm-12 text-center d-none d-sm-block">
<SwitchAccount
v-if="0 === index && allowSwitch"
v-on="$listeners"
:index="index"
:transaction-type="transactionType"
/>
</div>
<!-- destination -->
<div class="col-xl-5 col-lg-5 col-md-12 col-sm-12 col-xs-12">
<!-- DESTINATION -->
<TransactionAccount
v-model="destinationAccount"
v-on="$listeners"
:destination-allowed-types="destinationAllowedTypes"
:errors="transaction.errors.destination"
:index="index"
ref="destinationAccount"
:transaction-type="transactionType"
:source-allowed-types="sourceAllowedTypes"
direction="destination"
/>
</div>
</div>
<!-- amount -->
<div class="row">
<div class="col-xl-5 col-lg-5 col-md-10 col-sm-12 col-xs-12">
<!-- AMOUNT -->
<TransactionAmount
v-on="$listeners"
ref="amount"
:amount="transaction.amount"
:destination-currency-symbol="this.transaction.destination_account_currency_symbol"
:errors="transaction.errors.amount"
:index="index"
:source-currency-symbol="this.transaction.source_account_currency_symbol"
:transaction-type="this.transactionType"
/>
</div>
<div class="col-xl-2 col-lg-2 col-md-2 col-sm-12 text-center d-none d-sm-block">
<TransactionForeignCurrency
v-model="transaction.foreign_currency_id"
v-on="$listeners"
:destination-currency-id="this.transaction.destination_account_currency_id"
:index="index"
:selected-currency-id="this.transaction.foreign_currency_id"
:source-currency-id="this.transaction.source_account_currency_id"
:transaction-type="this.transactionType"
/>
</div>
<div class="col-xl-5 col-lg-5 col-md-12 col-sm-12 col-xs-12">
<!--
The reason that TransactionAmount gets the symbols and
TransactionForeignAmount gets the ID's of the currencies is
because ultimately TransactionAmount doesn't decide which
currency id is submitted to Firefly III.
-->
<TransactionForeignAmount
v-model="transaction.foreign_amount"
v-on="$listeners"
:destination-currency-id="this.transaction.destination_account_currency_id"
:errors="transaction.errors.foreign_amount"
:index="index"
:selected-currency-id="this.transaction.foreign_currency_id"
:source-currency-id="this.transaction.source_account_currency_id"
:transaction-type="this.transactionType"
/>
</div>
</div>
<!-- dates -->
<div class="row">
<div class="col-xl-5 col-lg-5 col-md-12 col-sm-12 col-xs-12">
<TransactionDate
v-on="$listeners"
:date="splitDate"
:errors="transaction.errors.date"
:index="index"
/>
</div>
<div class="col-xl-5 col-lg-5 col-md-12 col-sm-12 col-xs-12 offset-xl-2 offset-lg-2">
<TransactionCustomDates
v-on="$listeners"
:book-date="transaction.book_date"
:custom-fields.sync="customFields"
:due-date="transaction.due_date"
:errors="transaction.errors.custom_dates"
:index="index"
:interest-date="transaction.interest_date"
:invoice-date="transaction.invoice_date"
:payment-date="transaction.payment_date"
:process-date="transaction.process_date"
/>
</div>
</div>
<!-- end of body -->
</div>
</div>
</div>
</div> <!-- end of basic card -->
<!-- card for meta -->
<div class="row">
<div class="col">
<div class="card">
<div class="card-header">
<h3 class="card-title">
{{ $t('firefly.transaction_journal_meta') }}
<span v-if="count > 1">({{ index + 1 }} / {{ count }}) </span>
</h3>
</div>
<div class="card-body">
<!-- start of body -->
<!-- meta -->
<div class="row">
<div class="col-xl-6 col-lg-6 col-md-12 col-sm-12 col-xs-12">
<TransactionBudget
v-if="!('Transfer' === transactionType || 'Deposit' === transactionType)"
v-model="transaction.budget_id"
v-on="$listeners"
:errors="transaction.errors.budget"
:index="index"
/>
<TransactionCategory
v-model="transaction.category"
v-on="$listeners"
:errors="transaction.errors.category"
:index="index"
/>
</div>
<div class="col-xl-6 col-lg-6 col-md-12 col-sm-12 col-xs-12">
<TransactionBill
v-if="!('Transfer' === transactionType || 'Deposit' === transactionType)"
v-model="transaction.bill_id"
v-on="$listeners"
:errors="transaction.errors.bill"
:index="index"
/>
<TransactionTags
v-model="transaction.tags"
v-on="$listeners"
ref="tags"
:errors="transaction.errors.tags"
:index="index"
/>
<TransactionPiggyBank
v-if="!('Withdrawal' === transactionType || 'Deposit' === transactionType)"
v-model="transaction.piggy_bank_id"
v-on="$listeners"
:errors="transaction.errors.piggy_bank"
:index="index"
/>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- end card for meta -->
<!-- card for extra -->
<div v-if="hasMetaFields" class="row">
<div class="col">
<div class="card">
<div class="card-header">
<h3 class="card-title">
{{ $t('firefly.transaction_journal_extra') }}
<span v-if="count > 1">({{ index + 1 }} / {{ count }}) </span>
</h3>
</div>
<div class="card-body">
<!-- start of body -->
<div class="row">
<div class="col-xl-6 col-lg-6 col-md-12 col-sm-12 col-xs-12">
<TransactionInternalReference
v-model="transaction.internal_reference"
v-on="$listeners"
:custom-fields.sync="customFields"
:errors="transaction.errors.internal_reference"
:index="index"
/>
<TransactionExternalUrl
v-model="transaction.external_url"
v-on="$listeners"
:custom-fields.sync="customFields"
:errors="transaction.errors.external_url"
:index="index"
/>
<TransactionNotes
v-model="transaction.notes"
v-on="$listeners"
:custom-fields.sync="customFields"
:errors="transaction.errors.notes"
:index="index"
/>
</div>
<div class="col-xl-6 col-lg-6 col-md-12 col-sm-12 col-xs-12">
<TransactionAttachments
ref="attachments"
v-model="transaction.attachments"
v-on="$listeners"
:custom-fields.sync="customFields"
:index="index"
:transaction_journal_id="transaction.transaction_journal_id"
:upload-trigger="transaction.uploadTrigger"
:clear-trigger="transaction.clearTrigger"
/>
<TransactionLocation
v-model="transaction.location"
v-on="$listeners"
:custom-fields.sync="customFields"
:errors="transaction.errors.location"
:index="index"
/>
<TransactionLinks
v-model="transaction.links"
v-on="$listeners"
:custom-fields.sync="customFields"
:index="index"
/>
</div>
</div>
<!-- end of body -->
</div>
</div>
</div>
</div>
<!-- end card for extra -->
<!-- end of card -->
</div>
</template>
<script>
import TransactionDescription from "./TransactionDescription";
import TransactionDate from "./TransactionDate";
import TransactionBudget from "./TransactionBudget";
import TransactionAccount from "./TransactionAccount";
import SwitchAccount from "./SwitchAccount";
import TransactionAmount from "./TransactionAmount";
import TransactionForeignAmount from "./TransactionForeignAmount";
import TransactionForeignCurrency from "./TransactionForeignCurrency";
import TransactionCustomDates from "./TransactionCustomDates";
import TransactionCategory from "./TransactionCategory";
import TransactionBill from "./TransactionBill";
import TransactionTags from "./TransactionTags";
import TransactionPiggyBank from "./TransactionPiggyBank";
import TransactionInternalReference from "./TransactionInternalReference";
import TransactionExternalUrl from "./TransactionExternalUrl";
import TransactionNotes from "./TransactionNotes";
import TransactionLinks from "./TransactionLinks";
import TransactionAttachments from "./TransactionAttachments";
import SplitPills from "./SplitPills";
import TransactionLocation from "./TransactionLocation";
export default {
name: "SplitForm",
props: {
transaction: {
type: Object,
required: true
},
count: {
type: Number,
required: true
},
customFields: {
type: Object,
required: false
},
index: {
type: Number,
required: true
},
date: {
type: String,
required: true
},
transactionType: {
type: String,
required: true
},
sourceAllowedTypes: {
type: Array,
required: false,
default: function () {
return [];
}
}, // allowed source account types.
destinationAllowedTypes: {
type: Array,
required: false,
default: function () {
return [];
}
},
// allow switch?
allowSwitch: {
type: Boolean,
required: false,
default: false
}
},
created() {
// console.log('SplitForm(' + this.index + ')');
},
methods: {
removeTransaction: function () {
// console.log('Will remove transaction ' + this.index);
this.$emit('remove-transaction', {index: this.index});
},
triggerNextAccount: function (e) {
//alert(e);
if ('source' === e) {
// console.log('Jump to destination!');
this.$refs.destinationAccount.giveFocus();
}
},
},
computed: {
splitDate: function () {
return this.date;
},
sourceAccount: function () {
//console.log('computed::sourceAccount(' + this.index + ')');
return {
id: this.transaction.source_account_id,
name: this.transaction.source_account_name,
type: this.transaction.source_account_type,
};
//console.log(JSON.stringify(value));
//return value;
},
destinationAccount: function () {
//console.log('computed::destinationAccount(' + this.index + ')');
return {
id: this.transaction.destination_account_id,
name: this.transaction.destination_account_name,
type: this.transaction.destination_account_type,
};
//console.log(JSON.stringify(value));
//return value;
},
hasMetaFields: function () {
let requiredFields = [
'internal_reference',
'notes',
'attachments',
'external_uri',
'location',
'links',
];
for (let field in this.customFields) {
if (this.customFields.hasOwnProperty(field)) {
if (requiredFields.includes(field)) {
if (true === this.customFields[field]) {
return true;
}
}
}
}
return false;
}
},
components: {
TransactionLocation,
SplitPills,
TransactionAttachments,
TransactionNotes,
TransactionExternalUrl,
TransactionInternalReference,
TransactionPiggyBank,
TransactionTags,
TransactionLinks,
TransactionBill,
TransactionCategory,
TransactionCustomDates,
TransactionForeignCurrency,
TransactionForeignAmount,
TransactionAmount,
SwitchAccount,
TransactionAccount,
TransactionBudget,
TransactionDescription,
TransactionDate
},
}
</script>

View File

@@ -1,57 +0,0 @@
<!--
- SplitPills.vue
- 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/>.
-->
<template>
<div>
<div v-if="transactions.length > 1" class="row">
<div class="col">
<!-- tabs -->
<ul class="nav nav-pills ml-auto p-2" id="transactionTabs">
<li v-for="(transaction, index) in this.transactions" class="nav-item"><a :class="'nav-link' + (0 === index ? ' active' : '')"
:href="'#split_' + index"
:id="'tab_split_' + index"
data-toggle="pill">
<span v-if="'' !== transaction.description">{{ transaction.description }}</span>
<span v-if="'' === transaction.description">Split {{ index + 1 }}</span>
</a></li>
</ul>
</div>
</div>
</div>
</template>
<script>
export default {
name: "SplitPills",
props: {
transactions: {
type: Array,
required: true,
default: function () {
return [];
}
},
count: {
type: Number,
required: true
},
}
}
</script>

View File

@@ -1,43 +0,0 @@
<!--
- SwitchAccount.vue
- Copyright (c) 2020 james@firefly-iii.org
-
- This file is part of Firefly III (https://github.com/firefly-iii).
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div class="form-group">
<div class="text-xs d-none d-lg-block d-xl-block">
<span v-if="'any' !== this.transactionType" class="text-muted">
{{ $t('firefly.' + this.transactionType) }}
</span>
<span v-if="'any' === this.transactionType" class="text-muted">&nbsp;</span>
</div>
</div>
</template>
<script>
export default {
name: "SwitchAccount",
props: ['index', 'transactionType'],
methods: {
// switchAccounts() {
// this.$emit('switch-accounts', this.index);
// }
}
}
</script>

View File

@@ -1,309 +0,0 @@
<!--
- TransactionAccount.vue
- Copyright (c) 2020 james@firefly-iii.org
-
- This file is part of Firefly III (https://github.com/firefly-iii).
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div class="form-group">
<div v-if="visible" class="text-xs d-none d-lg-block d-xl-block">
<span v-if="0 === this.index">{{ $t('firefly.' + this.direction + '_account') }}</span>
<span v-if="this.index > 0" class="text-warning">{{ $t('firefly.first_split_overrules_' + this.direction) }}</span>
</div>
<div v-if="!visible" class="text-xs d-none d-lg-block d-xl-block">
&nbsp;
</div>
<vue-typeahead-bootstrap
v-if="visible"
v-model="accountName"
:data="accounts"
:inputClass="errors.length > 0 ? 'is-invalid' : ''"
:inputName="direction + '[]'"
:minMatchingChars="3"
:placeholder="$t('firefly.' + direction + '_account')"
:serializer="item => item.name_with_balance"
:showOnFocus=true
ref="inputThing"
aria-autocomplete="none"
autocomplete="off"
@hit="userSelectedAccount"
@input="lookupAccount"
>
<template slot="suggestion" slot-scope="{ data, htmlText }">
<div :title="data.type" class="d-flex">
<span v-html="htmlText"></span><br>
</div>
</template>
<template slot="append">
<div class="input-group-append">
<button class="btn btn-outline-secondary" tabindex="-1" type="button" v-on:click="clearAccount"><span class="far fa-trash-alt"></span></button>
</div>
</template>
</vue-typeahead-bootstrap>
<div v-if="!visible" class="form-control-static">
<span class="small text-muted"><em>{{ $t('firefly.first_split_decides') }}</em></span>
</div>
<span v-if="errors.length > 0">
<span v-for="error in errors" class="text-danger small">{{ error }}<br/></span>
</span>
</div>
</template>
<script>
import VueTypeaheadBootstrap from 'vue-typeahead-bootstrap';
import {debounce} from 'lodash';
export default {
name: "TransactionAccount",
components: {VueTypeaheadBootstrap},
props: {
index: {
type: Number,
},
direction: {
type: String,
},
value: {
type: Object,
default: () => ({})
},
errors: {
type: Array,
default: () => ([])
},
sourceAllowedTypes: {
type: Array,
default: () => ([])
},
destinationAllowedTypes: {
type: Array,
default: () => ([])
},
transactionType: {
type: String,
default: 'any'
},
},
data() {
return {
query: '',
accounts: [],
accountTypes: [],
initialSet: [],
selectedAccount: {},
accountName: '',
selectedAccountTrigger: false,
}
},
created() {
this.accountName = this.value.name ?? '';
// console.log('TransactionAccount::created() direction=' + this.direction + ', type=' + this.transactionType + ' , name="' + this.accountName + '"');
this.selectedAccountTrigger = true;
},
mounted: function () {
this.$nextTick(function () {
this.$refs.inputThing.$refs.input.tabIndex = 2;
})
},
methods: {
getACURL: function (types, query) {
return './api/v1/autocomplete/accounts?types=' + types.join(',') + '&query=' + query;
},
giveFocus: function () {
// console.log('I want focus! now OK: ' + this.direction + ' l: ' + this.accounts.length);
//console.log(this.$refs.inputThing.$refs.input.value);
this.$refs.inputThing.$refs.input.focus();
//console.log(this.$refs.inputThing.isFocused);
},
userSelectedAccount: function (event) {
// console.log('userSelectedAccount!');
// console.log('To prevent invalid propogation, set selectedAccountTrigger = true');
this.selectedAccountTrigger = true;
this.selectedAccount = event;
},
systemReturnedAccount: function (event) {
//console.log('systemReturnedAccount!');
//console.log('To prevent invalid propogation, set selectedAccountTrigger = false');
this.selectedAccountTrigger = false;
this.selectedAccount = event;
},
clearAccount: function () {
//// console.log('TransactionAccount::clearAccount()');
this.accounts = this.initialSet;
//this.account = {name: '', type: 'no_type', id: null, currency_id: null, currency_code: null, currency_symbol: null};
this.accountName = '';
},
lookupAccount: debounce(function () {
//// console.log('TransactionAccount::lookupAccount()');
//// console.log('In lookupAccount()');
if (0 === this.accountTypes.length) {
// set the types from the default types for this direction:
this.accountTypes = 'source' === this.direction ? this.sourceAllowedTypes : this.destinationAllowedTypes;
}
// // console.log(this.direction + ': Will search for types:');
// // console.log(this.accountTypes);
// update autocomplete URL:
axios.get(this.getACURL(this.accountTypes, this.accountName))
.then(response => {
//// console.log('Got a response!');
this.accounts = response.data;
//// console.log(response.data);
})
}, 300),
createInitialSet: function () {
//// console.log('TransactionAccount::createInitialSet()');
let types = this.sourceAllowedTypes;
if ('destination' === this.direction) {
types = this.destinationAllowedTypes;
}
// // console.log('createInitialSet() direction=' + this.direction + ' resets to these types:');
// // console.log(types);
axios.get(this.getACURL(types, ''))
.then(response => {
this.accounts = response.data;
this.initialSet = response.data;
});
}
},
watch: {
sourceAllowedTypes: function (value) {
//// console.log('TransactionAccount::sourceAllowedTypes()');
// // console.log(this.direction + ' account noticed change in sourceAllowedTypes');
// // console.log(value);
this.createInitialSet();
},
destinationAllowedTypes: function (value) {
//// console.log('TransactionAccount::destinationAllowedTypes()');
// // console.log(this.direction + ' account noticed change in destinationAllowedTypes');
// // console.log(value);
this.createInitialSet();
},
/**
* Triggered when the user selects an account from the auto-complete.
*
* @param value
*/
selectedAccount: function (value) {
//console.log('TransactionAccount::watch selectedAccount()');
// console.log(value);
if (true === this.selectedAccountTrigger) {
// console.log('$emit alles!');
this.$emit('set-account',
{
index: this.index,
direction: this.direction,
id: value.id,
type: value.type,
name: value.name,
currency_id: value.currency_id,
currency_code: value.currency_code,
currency_symbol: value.currency_symbol,
user_selected: true,
}
// jump to next field somehow.
);
//console.log('watch::selectedAccount() will now set accountName because selectedAccountTrigger = true');
this.accountName = value.name;
}
if (false === this.selectedAccountTrigger) {
//console.log('watch::selectedAccount() will NOT set accountName because selectedAccountTrigger = false');
}
if (false === this.selectedAccountTrigger && this.accountName !== value.name && null !== value.name) {
//console.log('watch::selectedAccount() will set accountName. selectedAccountTrigger = false but name is different ("' + this.accountName + '" > "' + value.name + '")');
this.selectedAccountTrigger = true;
this.accountName = value.name;
}
},
accountName: function (value) {
// console.log('now at watch accountName("' + value + '")');
// console.log(this.selectedAccountTrigger);
if (true === this.selectedAccountTrigger) {
// console.log('Do nothing because selectedAccountTrigger = true');
}
if (false === this.selectedAccountTrigger) {
// console.log('$emit name from watch::accountName() because selectedAccountTrigger = false');
this.$emit('set-account',
{
index: this.index,
direction: this.direction,
id: null,
type: null,
name: value,
currency_id: null,
currency_code: null,
currency_symbol: null,
user_selected: false
}
);
// this.account = {name: value, type: null, id: null, currency_id: null, currency_code: null, currency_symbol: null};
}
// console.log('set selectedAccountTrigger to be FALSE');
this.selectedAccountTrigger = false;
},
value: function (value) {
//console.log('TransactionAccount(' + this.index + ')::watch value(' + JSON.stringify(value) + ')');
this.systemReturnedAccount(value);
// // console.log('Index ' + this.index + ' nwAct: ', value);
// // console.log(this.direction + ' account overruled by external forces.');
// // console.log(value);
//this.account = value;
//this.selectedAccountTrigger = true;
// this.accountName = value.name ?? '';
// if(null !== value.id) {
// return;
// }
// this.selectedAccountTrigger = true;
//
// // console.log('Set selectedAccountTrigger = true');
// this.selectedAccount = value;
}
},
computed: {
accountKey: {
get() {
return 'source' === this.direction ? 'source_account' : 'destination_account';
}
},
visible: {
get() {
// index 0 is always visible:
if (0 === this.index) {
return true;
}
// // console.log('Direction of account ' + this.index + ' is ' + this.direction + '(' + this.transactionType + ')');
// // console.log(this.transactionType);
if ('source' === this.direction) {
return 'any' === this.transactionType || 'Deposit' === this.transactionType || typeof this.transactionType === 'undefined';
}
if ('destination' === this.direction) {
return 'any' === this.transactionType || 'Withdrawal' === this.transactionType || typeof this.transactionType === 'undefined';
}
return false;
}
}
}
}
</script>

View File

@@ -1,123 +0,0 @@
<!--
- TransactionAmount.vue
- Copyright (c) 2020 james@firefly-iii.org
-
- This file is part of Firefly III (https://github.com/firefly-iii).
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div class="form-group">
<div class="text-xs">{{ $t('firefly.amount') }}</div>
<div class="input-group">
<div v-if="currencySymbol" class="input-group-prepend">
<div class="input-group-text">{{ currencySymbol }}</div>
</div>
<input
v-model="transactionAmount"
:class="errors.length > 0 ? 'form-control is-invalid' : 'form-control'"
:placeholder="$t('firefly.amount')"
:title="$t('firefly.amount')"
autocomplete="off"
name="amount[]"
type="number"
ref="input"
step="any"
>
</div>
<span v-if="errors.length > 0">
<span v-for="error in errors" class="text-danger small">{{ error }}<br/></span>
</span>
</div>
</template>
<script>
export default {
name: "TransactionAmount",
props: {
index: {
type: Number,
default: 0,
required: true
},
errors: {},
amount: {},
transactionType: {},
sourceCurrencySymbol: {},
destinationCurrencySymbol: {},
fractionDigits: {
default: 2,
required: false
},
},
created() {
if ('' !== this.amount) {
this.emitEvent = false;
this.transactionAmount = this.formatNumber(this.amount);
}
},
mounted: function () {
this.$nextTick(function () {
this.$refs.input.tabIndex = 3;
})
},
methods: {
formatNumber(str) {
return parseFloat(str).toFixed(this.fractionDigits);
},
giveFocus: function () {
this.$refs.input.focus();
},
},
data() {
return {
transactionAmount: this.amount,
currencySymbol: null,
srcCurrencySymbol: this.sourceCurrencySymbol,
dstCurrencySymbol: this.destinationCurrencySymbol,
emitEvent: true
}
},
watch: {
transactionAmount: function (value) {
if (true === this.emitEvent) {
this.$emit('set-field', {field: 'amount', index: this.index, value: value});
}
this.emitEvent = true;
},
amount: function (value) {
this.transactionAmount = value;
},
sourceCurrencySymbol: function (value) {
this.srcCurrencySymbol = value;
},
destinationCurrencySymbol: function (value) {
this.dstCurrencySymbol = value;
},
transactionType: function (value) {
switch (value) {
case 'Transfer':
case 'Withdrawal':
this.currencySymbol = this.srcCurrencySymbol;
break;
case 'Deposit':
this.currencySymbol = this.dstCurrencySymbol;
}
},
},
}
</script>

View File

@@ -1,140 +0,0 @@
<!--
- TransactionAttachments.vue
- 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/>.
-->
<template>
<div v-if="showField" class="form-group">
<div class="text-xs d-none d-lg-block d-xl-block">
{{ $t('firefly.attachments') }}
</div>
<div class="input-group">
<input
ref="att"
class="form-control"
multiple
@change="selectedFile"
name="attachments[]"
type="file"
/>
</div>
</div>
</template>
<script>
export default {
name: "TransactionAttachments",
props: ['transaction_journal_id', 'customFields', 'index', 'uploadTrigger', 'clearTrigger'],
data() {
return {
availableFields: this.customFields,
uploads: 0,
created: 0,
uploaded: 0,
}
},
watch: {
customFields: function (value) {
this.availableFields = value;
},
uploadTrigger: function () {
// console.log('uploadTrigger(' + this.transaction_journal_id + ',' + this.index + ')');
this.doUpload();
},
clearTrigger: function () {
// console.log('clearTrigger(' + this.transaction_journal_id + ',' + this.index + ')');
this.$refs.att.value = null;
},
transaction_journal_id: function (value) {
// console.log('watch transaction_journal_id: ' + value + ' (index ' + this.index + ')');
}
},
computed: {
showField: function () {
if ('attachments' in this.availableFields) {
return this.availableFields.attachments;
}
return false;
}
},
methods: {
selectedFile: function () {
this.$emit('selected-attachments', {index: this.index, id: this.transaction_journal_id});
},
createAttachment: function (name) {
// console.log('Now in createAttachment()');
const uri = './api/v1/attachments';
const data = {
filename: name,
attachable_type: 'TransactionJournal',
attachable_id: this.transaction_journal_id,
};
// create new attachment:
return axios.post(uri, data);
},
uploadAttachment: function (attachmentId, data) {
this.created++;
// console.log('Now in uploadAttachment()');
const uploadUri = './api/v1/attachments/' + attachmentId + '/upload';
return axios.post(uploadUri, data)
},
countAttachment: function () {
this.uploaded++;
// console.log('Uploaded ' + this.uploaded + ' / ' + this.uploads);
if (this.uploaded >= this.uploads) {
// console.log('All files uploaded. Emit event for ' + this.transaction_journal_id + '(' + this.index + ')');
this.$emit('uploaded-attachments', this.transaction_journal_id);
}
},
doUpload: function () {
let files = this.$refs.att.files;
this.uploads = files.length;
// loop all files and create attachments.
for (let i in files) {
if (files.hasOwnProperty(i) && /^0$|^[1-9]\d*$/.test(i) && i <= 4294967294) {
// console.log('Now at file ' + (parseInt(i) + 1) + ' / ' + files.length);
// read file into file reader:
let current = files[i];
let fileReader = new FileReader();
let theParent = this; // dont ask me why i need to do this.
fileReader.onloadend = evt => {
if (evt.target.readyState === FileReader.DONE) {
// console.log('I am done reading file ' + (parseInt(i) + 1));
this.createAttachment(current.name).then(response => {
// console.log('Created attachment. Now upload (1)');
return theParent.uploadAttachment(response.data.data.id, new Blob([evt.target.result]));
}).then(theParent.countAttachment);
}
}
fileReader.readAsArrayBuffer(current);
}
}
if (0 === files.length) {
//console.log('No files to upload. Emit event!');
this.$emit('uploaded-attachments', this.transaction_journal_id);
}
// Promise.all(promises).then(response => {
// console.log('All files uploaded. Emit event!');
// this.$emit('uploaded-attachments', this.transaction_journal_id);
// });
}
}
}
</script>

View File

@@ -1,105 +0,0 @@
<!--
- TransactionBill.vue
- 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/>.
-->
<template>
<div class="form-group">
<div class="text-xs d-none d-lg-block d-xl-block">
{{ $t('firefly.bill') }}
</div>
<div class="input-group">
<select
ref="bill"
v-model="bill"
:class="errors.length > 0 ? 'form-control is-invalid' : 'form-control'"
:title="$t('firefly.bill')"
autocomplete="off"
name="bill_id[]"
>
<option v-for="bill in this.billList" :label="bill.name" :value="bill.id">{{ bill.name }}</option>
</select>
</div>
<span v-if="errors.length > 0">
<span v-for="error in errors" class="text-danger small">{{ error }}<br/></span>
</span>
</div>
</template>
<script>
export default {
props: ['value', 'index', 'errors'],
name: "TransactionBill",
data() {
return {
billList: [],
bill: this.value,
}
},
mounted: function () {
this.$nextTick(function () {
this.$refs.bill.tabIndex = 9;
})
},
created() {
this.collectData();
},
methods: {
collectData() {
this.billList.push(
{
id: 0,
name: this.$t('firefly.no_bill'),
}
);
this.getBills();
},
getBills() {
axios.get('./api/v1/bills')
.then(response => {
this.parseBills(response.data);
}
);
},
parseBills(data) {
for (let key in data.data) {
if (data.data.hasOwnProperty(key) && /^0$|^[1-9]\d*$/.test(key) && key <= 4294967294) {
let current = data.data[key];
this.billList.push(
{
id: parseInt(current.id),
name: current.attributes.name
}
);
}
}
},
},
watch: {
value: function (value) {
this.emitEvent = false;
this.bill = value;
},
bill: function (value) {
this.$emit('set-field', {field: 'bill_id', index: this.index, value: value});
}
},
}
</script>

View File

@@ -1,107 +0,0 @@
<!--
- TransactionBudget.vue
- Copyright (c) 2020 james@firefly-iii.org
-
- This file is part of Firefly III (https://github.com/firefly-iii).
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div class="form-group">
<div class="text-xs d-none d-lg-block d-xl-block">
{{ $t('firefly.budget') }}
</div>
<div class="input-group">
<select
ref="budget"
v-model="budget"
:class="errors.length > 0 ? 'form-control is-invalid' : 'form-control'"
:title="$t('firefly.budget')"
autocomplete="off"
name="budget_id[]"
>
<option v-for="budget in this.budgetList" :label="budget.name" :value="budget.id">{{ budget.name }}</option>
</select>
</div>
<span v-if="errors.length > 0">
<span v-for="error in errors" class="text-danger small">{{ error }}<br/></span>
</span>
</div>
</template>
<script>
export default {
props: ['index', 'value', 'errors'],
name: "TransactionBudget",
data() {
return {
budgetList: [],
budget: this.value,
emitEvent: true
}
},
mounted: function () {
this.$nextTick(function () {
this.$refs.budget.tabIndex = 8;
})
},
created() {
this.collectData();
},
methods: {
collectData() {
this.budgetList.push(
{
id: 0,
name: this.$t('firefly.no_budget'),
}
);
this.getBudgets();
},
getBudgets() {
axios.get('./api/v1/budgets')
.then(response => {
this.parseBudgets(response.data);
}
);
},
parseBudgets(data) {
for (let key in data.data) {
if (data.data.hasOwnProperty(key) && /^0$|^[1-9]\d*$/.test(key) && key <= 4294967294) {
let current = data.data[key];
if(!current.attributes.active) {
continue;
}
this.budgetList.push(
{
id: parseInt(current.id),
name: current.attributes.name
}
);
}
}
},
},
watch: {
value: function (value) {
this.emitEvent = false;
this.budget = value;
},
budget: function (value) {
this.$emit('set-field', {field: 'budget_id', index: this.index, value: value});
}
},
}
</script>

View File

@@ -1,120 +0,0 @@
<!--
- TransactionCategory.vue
- 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/>.
-->
<template>
<div class="form-group">
<div class="text-xs d-none d-lg-block d-xl-block">
{{ $t('firefly.category') }}
</div>
<vue-typeahead-bootstrap
v-model="category"
:data="categories"
:inputClass="errors.length > 0 ? 'is-invalid' : ''"
:minMatchingChars="3"
:placeholder="$t('firefly.category')"
:serializer="item => item.name"
:showOnFocus=true
inputName="category[]"
@hit="selectedCategory = $event"
@input="lookupCategory"
ref="input"
>
<template slot="append">
<div class="input-group-append">
<button class="btn btn-outline-secondary" tabindex="-1" type="button" v-on:click="clearCategory"><span class="far fa-trash-alt"></span></button>
</div>
</template>
</vue-typeahead-bootstrap>
<span v-if="errors.length > 0">
<span v-for="error in errors" class="text-danger small">{{ error }}<br/></span>
</span>
</div>
</template>
<script>
import VueTypeaheadBootstrap from 'vue-typeahead-bootstrap';
import {debounce} from "lodash";
export default {
props: ['value', 'index', 'errors'],
components: {VueTypeaheadBootstrap},
name: "TransactionCategory",
data() {
return {
categories: [],
initialSet: [],
category: this.value
}
},
mounted: function () {
this.$nextTick(function () {
this.$refs.input.$refs.input.tabIndex = 10;
})
},
created() {
//console.log('Created category(' + this.index + ') "' + this.value + '"');
// initial list of accounts:
axios.get(this.getACURL(''))
.then(response => {
this.categories = response.data;
this.initialSet = response.data;
});
},
methods: {
clearCategory: function () {
this.category = '';
},
getACURL: function (query) {
// update autocomplete URL:
// console.log('getACURL("' + query + '")');
return document.getElementsByTagName('base')[0].href + 'api/v1/autocomplete/categories?query=' + query;
},
lookupCategory: debounce(function () {
// update autocomplete URL:
//console.log('Do a search for "'+this.category+'"');
axios.get(this.getACURL(this.category))
.then(response => {
this.categories = response.data;
})
}, 300)
},
watch: {
value: function (value) {
this.category = value ?? '';
},
category: function (value) {
this.$emit('set-field', {field: 'category', index: this.index, value: value});
}
},
computed: {
selectedCategory: {
get() {
return this.categories[this.index].name;
},
set(value) {
this.category = value.name;
}
}
}
}
</script>

View File

@@ -1,108 +0,0 @@
<!--
- TransactionCustomDates.vue
- 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/>.
-->
<template>
<div>
<div v-for="(enabled, name) in availableFields" class="form-group">
<div v-if="enabled && isDateField(name)" class="text-xs d-none d-lg-block d-xl-block">
{{ $t('form.' + name) }}
</div>
<div v-if="enabled && isDateField(name)" class="input-group">
<input
:ref="name"
:name="name + '[]'"
:placeholder="$t('form.' + name)"
:title="$t('form.' + name)"
:value="getFieldValue(name)"
autocomplete="off"
class="form-control"
type="date"
@change="setFieldValue($event, name)"
>
</div>
</div>
</div>
</template>
<script>
export default {
name: "TransactionCustomDates",
props: [
'index',
'errors',
'customFields',
'interestDate',
'bookDate',
'processDate',
'dueDate',
'paymentDate',
'invoiceDate'
],
data() {
return {
dateFields: ['interest_date', 'book_date', 'process_date', 'due_date', 'payment_date', 'invoice_date'],
availableFields: this.customFields,
dates: {
interest_date: this.interestDate,
book_date: this.bookDate,
process_date: this.processDate,
due_date: this.dueDate,
payment_date: this.paymentDate,
invoice_date: this.invoiceDate,
}
,
}
},
watch: {
customFields: function (value) {
this.availableFields = value;
},
interestDate: function (value) {
this.dates.interest_date = value;
},
bookDate: function (value) {
this.dates.book_date = value;
},
processDate: function (value) {
this.dates.process_date = value;
},
dueDate: function (value) {
this.dates.due_date = value;
},
paymentDate: function (value) {
this.dates.payment_date = value;
},
invoiceDate: function (value) {
this.dates.invoice_date = value;
},
},
methods: {
isDateField: function (name) {
return this.dateFields.includes(name)
},
getFieldValue(field) {
return this.dates[field] ?? '';
},
setFieldValue(event, field) {
this.$emit('set-field', {field: field, index: this.index, value: event.target.value});
},
}
}
</script>

View File

@@ -1,99 +0,0 @@
<!--
- TransactionDate.vue
- Copyright (c) 2020 james@firefly-iii.org
-
- This file is part of Firefly III (https://github.com/firefly-iii).
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div class="form-group" v-if="0===index">
<div class="text-xs d-none d-lg-block d-xl-block">
{{ $t('firefly.date_and_time') }}
</div>
<div class="input-group">
<input
ref="date"
v-model="dateStr"
:class="errors.length > 0 ? 'form-control is-invalid' : 'form-control'"
:placeholder="dateStr"
:title="$t('firefly.date')"
autocomplete="off"
name="date[]"
type="date"
>
<input
ref="time"
v-model="timeStr"
:class="errors.length > 0 ? 'form-control is-invalid' : 'form-control'"
:placeholder="timeStr"
:title="$t('firefly.time')"
autocomplete="off"
name="time[]"
type="time"
>
</div>
<span v-if="errors.length > 0">
<span v-for="error in errors" class="text-danger small">{{ error }}<br/></span>
</span>
<span class="text-muted small">{{ localTimeZone }}:{{ systemTimeZone }}</span>
</div>
</template>
<script>
import {mapGetters} from "vuex";
export default {
props: ['index', 'errors', 'date'],
name: "TransactionDate",
created() {
this.localTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
this.systemTimeZone = this.timezone;
// console.log('TransactionDate: ' + this.date);
// split date and time:
let parts = this.date.split('T');
this.dateStr = parts[0];
this.timeStr = parts[1];
},
mounted: function () {
this.$nextTick(function () {
this.$refs.date.tabIndex = 6;
this.$refs.time.tabIndex = 7;
});
},
data() {
return {
localDate: this.date,
localTimeZone: '',
systemTimeZone: '',
timeStr: '',
dateStr: '',
}
},
watch: {
dateStr: function (value) {
this.$emit('set-date', {date: value + 'T' + this.timeStr});
},
timeStr: function (value) {
this.$emit('set-date', {date: this.dateStr + 'T' + value});
}
},
methods: {},
computed: {
...mapGetters('root', ['timezone']),
}
}
</script>

View File

@@ -1,100 +0,0 @@
<!--
- TransactionDescription.vue
- Copyright (c) 2020 james@firefly-iii.org
-
- This file is part of Firefly III (https://github.com/firefly-iii).
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div class="form-group">
<vue-typeahead-bootstrap
v-model="description"
:data="descriptions"
:inputClass="errors.length > 0 ? 'is-invalid' : ''"
:minMatchingChars="3"
:placeholder="$t('firefly.description')"
:serializer="item => item.description"
:showOnFocus=true
autofocus
tabindex="1"
ref="autoComplete"
inputName="description[]"
@input="lookupDescription"
>
<template slot="append">
<div class="input-group-append">
<button class="btn btn-outline-secondary" tabindex="-1" type="button" v-on:click="clearDescription"><span class="far fa-trash-alt"></span></button>
</div>
</template>
</vue-typeahead-bootstrap>
<span v-if="errors.length > 0">
<span v-for="error in errors" class="text-danger small">{{ error }}<br/></span>
</span>
</div>
</template>
<script>
import VueTypeaheadBootstrap from 'vue-typeahead-bootstrap';
import {debounce} from "lodash";
export default {
props: ['index', 'value', 'errors'],
components: {VueTypeaheadBootstrap},
name: "TransactionDescription",
data() {
return {
descriptions: [],
initialSet: [],
description: this.value,
}
},
created() {
axios.get(this.getACURL(''))
.then(response => {
this.descriptions = response.data;
this.initialSet = response.data;
this.$refs.autoComplete.$refs.input.tabIndex = 1;
});
},
methods: {
clearDescription: function () {
this.description = '';
},
getACURL: function (query) {
// update autocomplete URL:
return document.getElementsByTagName('base')[0].href + 'api/v1/autocomplete/transactions?query=' + query;
},
lookupDescription: debounce(function () {
// update autocomplete URL:
axios.get(this.getACURL(this.value))
.then(response => {
this.descriptions = response.data;
})
}, 300)
},
watch: {
value: function (value) {
this.description = value;
},
description: function (value) {
this.$emit('set-field', {field: 'description', index: this.index, value: value});
}
},
}
</script>

View File

@@ -1,73 +0,0 @@
<!--
- TransactionInternalReference.vue
- 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/>.
-->
<template>
<div v-if="showField" class="form-group">
<div class="text-xs d-none d-lg-block d-xl-block">
{{ $t('firefly.external_url') }}
</div>
<div class="input-group">
<input
v-model="url"
:class="errors.length > 0 ? 'form-control is-invalid' : 'form-control'"
:placeholder="$t('firefly.external_url')"
name="external_url[]"
type="url"
/>
<div class="input-group-append">
<button class="btn btn-outline-secondary" tabindex="-1" type="button"><span class="far fa-trash-alt"></span></button>
</div>
</div>
</div>
</template>
<script>
export default {
props: ['index', 'value', 'errors', 'customFields'],
name: "TransactionExternalUrl",
data() {
return {
url: this.value,
availableFields: this.customFields,
}
},
computed: {
showField: function () {
if ('external_uri' in this.availableFields) {
return this.availableFields.external_uri;
}
return false;
}
},
methods: {},
watch: {
customFields: function (value) {
this.availableFields = value;
},
value: function (value) {
this.url = value;
},
url: function (value) {
this.$emit('set-field', {field: 'external_url', index: this.index, value: value});
}
}
}
</script>

View File

@@ -1,102 +0,0 @@
<!--
- TransactionForeignAmount.vue
- Copyright (c) 2020 james@firefly-iii.org
-
- This file is part of Firefly III (https://github.com/firefly-iii).
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<!-- FOREIGN AMOUNT -->
<div v-if="isVisible" class="form-group">
<div class="text-xs">{{ $t('form.foreign_amount') }}</div>
<div class="input-group">
<input
v-model="amount"
:class="errors.length > 0 ? 'form-control is-invalid' : 'form-control'"
:placeholder="$t('form.foreign_amount')"
:title="$t('form.foreign_amount')"
autocomplete="off"
name="foreign_amount[]"
type="number"
ref="input"
>
</div>
<span v-if="errors.length > 0">
<span v-for="error in errors" class="text-danger small">{{ error }}<br/></span>
</span>
</div>
</template>
<script>
export default {
name: "TransactionForeignAmount",
props: {
index: {},
errors: {},
value: {},
transactionType: {},
sourceCurrencyId: {},
destinationCurrencyId: {},
fractionDigits: {
type: Number,
default: 2
}
},
data() {
return {
amount: this.value,
emitEvent: true
}
},
created() {
if ('' !== this.amount) {
this.emitEvent = false;
this.amount = this.formatNumber(this.amount);
}
},
mounted: function () {
this.$nextTick(function () {
this.$refs.input.tabIndex = 5;
})
},
methods: {
formatNumber(str) {
return parseFloat(str).toFixed(this.fractionDigits);
}
},
watch: {
amount: function (value) {
if (true === this.emitEvent) {
this.$emit('set-field', {field: 'foreign_amount', index: this.index, value: value});
}
this.emitEvent = true;
},
value: function (value) {
this.amount = value;
}
},
computed: {
isVisible: {
get() {
return !('transfer' === this.transactionType.toLowerCase() && parseInt(this.sourceCurrencyId) === parseInt(this.destinationCurrencyId));
}
},
}
}
</script>

View File

@@ -1,146 +0,0 @@
<!--
- TransactionForeignCurrency.vue
- Copyright (c) 2020 james@firefly-iii.org
-
- This file is part of Firefly III (https://github.com/firefly-iii).
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<!-- FOREIGN Currency -->
<div v-if="isVisible" class="form-group">
<div class="text-xs">&nbsp;</div>
<div class="input-group">
<select v-model="selectedCurrency" class="form-control" ref="input" name="foreign_currency_id[]">
<option v-for="currency in selectableCurrencies" :label="currency.name" :value="currency.id">{{ currency.name }}</option>
</select>
</div>
</div>
</template>
<script>
export default {
name: "TransactionForeignCurrency",
props: [
'index',
'transactionType',
'sourceCurrencyId',
'destinationCurrencyId',
'selectedCurrencyId',
'value'
],
data() {
return {
selectedCurrency: this.value,
allCurrencies: [],
selectableCurrencies: [],
dstCurrencyId: this.destinationCurrencyId,
srcCurrencyId: this.sourceCurrencyId,
lockedCurrency: 0,
emitEvent: true
}
},
watch: {
value: function (value) {
this.selectedCurrency = value;
},
sourceCurrencyId: function (value) {
// console.log('Watch sourceCurrencyId');
this.srcCurrencyId = value;
this.lockCurrency();
},
destinationCurrencyId: function (value) {
// console.log('Watch destinationCurrencyId');
this.dstCurrencyId = value;
this.lockCurrency();
},
selectedCurrency: function (value) {
this.$emit('set-field', {field: 'foreign_currency_id', index: this.index, value: value});
},
transactionType: function (value) {
this.lockCurrency();
},
},
created: function () {
// console.log('Created TransactionForeignCurrency');
this.getAllCurrencies();
},
mounted: function () {
this.$nextTick(function () {
this.$refs.input.tabIndex = 4;
})
},
methods: {
lockCurrency: function () {
// console.log('Lock currency (' + this.transactionType + ')');
this.lockedCurrency = 0;
if ('transfer' === this.transactionType.toLowerCase()) {
// console.log('IS a transfer!');
this.lockedCurrency = parseInt(this.dstCurrencyId);
this.selectedCurrency = parseInt(this.dstCurrencyId);
}
this.filterCurrencies();
},
getAllCurrencies: function () {
axios.get('./api/v1/autocomplete/currencies')
.then(response => {
this.allCurrencies = response.data;
this.filterCurrencies();
}
);
},
filterCurrencies() {
// console.log('filterCurrencies');
// console.log(this.lockedCurrency);
// if a currency is locked only that currency can (and must) be selected:
if (0 !== this.lockedCurrency) {
// console.log('Here we are');
for (let key in this.allCurrencies) {
if (this.allCurrencies.hasOwnProperty(key) && /^0$|^[1-9]\d*$/.test(key) && key <= 4294967294) {
let current = this.allCurrencies[key];
if (parseInt(current.id) === this.lockedCurrency) {
this.selectableCurrencies = [current];
this.selectedCurrency = current.id;
}
}
}
// if source + dest ID are the same, skip the whole field.
return;
}
this.selectableCurrencies = [
{
"id": 0,
"name": this.$t('firefly.no_currency')
}
];
for (let key in this.allCurrencies) {
if (this.allCurrencies.hasOwnProperty(key) && /^0$|^[1-9]\d*$/.test(key) && key <= 4294967294) {
let current = this.allCurrencies[key];
this.selectableCurrencies.push(current);
}
}
}
},
computed: {
isVisible: function () {
return !('transfer' === this.transactionType.toLowerCase() && parseInt(this.srcCurrencyId) === parseInt(this.dstCurrencyId));
}
}
}
</script>

View File

@@ -1,101 +0,0 @@
<!--
- TransactionGroupTitle.vue
- 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/>.
-->
<template>
<div class="form-group">
<div class="text-xs d-none d-lg-block d-xl-block">
{{ $t('firefly.split_transaction_title') }}
</div>
<vue-typeahead-bootstrap
v-model="title"
:data="descriptions"
:inputClass="errors.length > 0 ? 'is-invalid' : ''"
:minMatchingChars="3"
:placeholder="$t('firefly.split_transaction_title')"
:serializer="item => item.description"
:showOnFocus=true
inputName="group_title"
@input="lookupDescription"
>
<template slot="append">
<div class="input-group-append">
<button class="btn btn-outline-secondary" tabindex="-1" type="button" v-on:click="clearDescription"><span class="far fa-trash-alt"></span></button>
</div>
</template>
</vue-typeahead-bootstrap>
<span v-if="errors.length > 0">
<span v-for="error in errors" class="text-danger small">{{ error }}<br/></span>
</span>
</div>
</template>
<script>
import VueTypeaheadBootstrap from 'vue-typeahead-bootstrap';
import {debounce} from "lodash";
export default {
props: ['value', 'errors'],
name: "TransactionGroupTitle",
components: {VueTypeaheadBootstrap},
data() {
return {
descriptions: [],
initialSet: [],
title: this.value,
emitEvent: true
}
},
created() {
axios.get(this.getACURL(''))
.then(response => {
this.descriptions = response.data;
this.initialSet = response.data;
});
},
watch: {
value: function (value) {
this.title = value;
},
title: function (value) {
this.$emit('set-group-title', value);
}
},
methods: {
clearDescription: function () {
this.title = '';
},
getACURL: function (query) {
// update autocomplete URL:
return document.getElementsByTagName('base')[0].href + 'api/v1/autocomplete/transactions?query=' + query;
},
lookupDescription: debounce(function () {
// update autocomplete URL:
axios.get(this.getACURL(this.title))
.then(response => {
this.descriptions = response.data;
})
}, 300)
}
}
</script>

View File

@@ -1,74 +0,0 @@
<!--
- TransactionInternalReference.vue
- 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/>.
-->
<template>
<div v-if="showField" class="form-group">
<div class="text-xs d-none d-lg-block d-xl-block">
{{ $t('firefly.internal_reference') }}
</div>
<div class="input-group">
<input
v-model="reference"
:class="errors.length > 0 ? 'form-control is-invalid' : 'form-control'"
:placeholder="$t('firefly.internal_reference')"
name="internal_reference[]"
type="text"
/>
<div class="input-group-append">
<button class="btn btn-outline-secondary" tabindex="-1" type="button"><span class="far fa-trash-alt"></span></button>
</div>
</div>
</div>
</template>
<script>
export default {
props: ['index', 'value', 'errors', 'customFields'],
name: "TransactionInternalReference",
data() {
return {
reference: this.value,
availableFields: this.customFields,
emitEvent: true
}
},
computed: {
showField: function () {
if ('internal_reference' in this.availableFields) {
return this.availableFields.internal_reference;
}
return false;
}
},
methods: {},
watch: {
customFields: function (value) {
this.availableFields = value;
},
value: function (value) {
this.emitEvent = false;
this.reference = value;
},
reference: function (value) {
this.$emit('set-field', {field: 'internal_reference', index: this.index, value: value});
}
}
}
</script>

View File

@@ -1,390 +0,0 @@
<!--
- TransactionLinks.vue
- 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/>.
-->
<template>
<div v-if="showField">
<div class="form-group">
<div class="text-xs d-none d-lg-block d-xl-block">
{{ $t('firefly.journal_links') }}
</div>
<div class="row">
<div class="col">
<p v-if="links.length === 0">
<button type="button" class="btn btn-default btn-xs" data-target="#linkModal" @click="resetModal" data-toggle="modal"><span class="fas fa-plus"></span> Add transaction link</button>
</p>
<ul v-if="links.length > 0" class="list-group">
<li v-for="(transaction, index) in links" class="list-group-item" v-bind:key="index">
<em>{{ getTextForLinkType(transaction.link_type_id) }}</em>
<a :href='"./transaction/show/" + transaction.transaction_group_id'>{{ transaction.description }}</a>
<span v-if="transaction.type === 'withdrawal'">
(<span class="text-danger">{{
Intl.NumberFormat(locale, {
style: 'currency',
currency: transaction.currency_code
}).format(parseFloat(transaction.amount) * -1)
}}</span>)
</span>
<span v-if="transaction.type === 'deposit'">
(<span class="text-success">{{
Intl.NumberFormat(locale, {
style: 'currency',
currency: transaction.currency_code
}).format(parseFloat(transaction.amount))
}}</span>)
</span>
<span v-if="transaction.type === 'transfer'">
(<span class="text-info">{{
Intl.NumberFormat(locale, {
style: 'currency',
currency: transaction.currency_code
}).format(parseFloat(transaction.amount))
}}</span>)
</span>
<div class="btn-group btn-group-xs float-right">
<button type="button" class="btn btn-xs btn-danger" @click="removeLink(index)" tabindex="-1"><span class="far fa-trash-alt"></span></button>
</div>
</li>
</ul>
<div v-if="links.length > 0" class="form-text">
<button type="button" class="btn btn-default" @click="resetModal" data-target="#linkModal" data-toggle="modal"><span class="fas fa-plus"></span></button>
</div>
</div>
</div>
</div>
<!-- modal -->
<div id="linkModal" class="modal" tabindex="-1" ref="linkModal">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Transaction thing dialog.</h5>
<button aria-label="Close" class="close" data-dismiss="modal" type="button">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="container-fluid">
<div class="row">
<div class="col">
<p>
Use this form to search for transactions you wish to link to this one. When in doubt, use <code>id:*</code> where the ID is the number from
the URL.
</p>
</div>
</div>
<div class="row">
<div class="col">
<form v-on:submit.prevent="search" autocomplete="off">
<div class="input-group">
<input id="query" v-model="query" autocomplete="off" class="form-control" maxlength="255" name="search"
placeholder="Search query" type="text">
<div class="input-group-append">
<button class="btn btn-default" type="submit"><span class="fas fa-search"></span> Search</button>
</div>
</div>
</form>
</div>
</div>
<div class="row">
<div class="col">
<span v-if="searching"><span class="fas fa-spinner fa-spin"></span></span>
<h4 v-if="searchResults.length > 0">{{ $t('firefly.search_results') }}</h4>
<table v-if="searchResults.length > 0" class="table table-sm">
<caption style="display:none;">{{ $t('firefly.search_results') }}</caption>
<thead>
<tr>
<th scope="col" colspan="2" style="width:33%">{{ $t('firefly.include') }}</th>
<th scope="col">{{ $t('firefly.transaction') }}</th>
</tr>
</thead>
<tbody>
<tr v-for="result in searchResults">
<td>
<input v-model="result.selected" class="form-control"
type="checkbox"
@change="selectTransaction($event)"
/>
</td>
<td>
<select
v-model="result.link_type_id"
class="form-control"
@change="selectLinkType($event)"
>
<option v-for="linkType in linkTypes" :label="linkType.type" :value="linkType.id + '-' + linkType.direction">{{
linkType.type
}}
</option>
</select>
</td>
<td>
<a :href="'./transactions/show/' + result.transaction_group_id">{{ result.description }}</a>
<span v-if="result.type === 'withdrawal'">
(<span class="text-danger">{{
Intl.NumberFormat(locale, {
style: 'currency',
currency: result.currency_code
}).format(parseFloat(result.amount) * -1)
}}</span>)
</span>
<span v-if="result.type === 'deposit'">
(<span class="text-success">{{
Intl.NumberFormat(locale, {
style: 'currency',
currency: result.currency_code
}).format(parseFloat(result.amount))
}}</span>)
</span>
<span v-if="result.type === 'transfer'">
(<span class="text-info">{{
Intl.NumberFormat(locale, {
style: 'currency',
currency: result.currency_code
}).format(parseFloat(result.amount))
}}</span>)
</span>
<br/>
<em>
<a :href="'./accounts/show/' + result.source_id">{{ result.source_name }}</a>
&rarr;
<a :href="'./accounts/show/' + result.destination_id">{{ result.destination_name }}</a>
</em>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" data-dismiss="modal" type="button">Close</button>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
const lodashClonedeep = require('lodash.clonedeep');
// See reference nr. 3
export default {
props: ['index', 'value', 'errors', 'customFields'],
name: "TransactionLinks",
data() {
return {
searchResults: [],
include: [],
locale: 'en-US',
linkTypes: [],
query: '',
searching: false,
links: this.value,
availableFields: this.customFields,
emitEvent: true
}
},
created() {
this.locale = localStorage.locale ?? 'en-US';
this.emitEvent = false;
this.links = lodashClonedeep(this.value);
this.getLinkTypes();
},
computed: {
showField: function () {
if ('links' in this.availableFields) {
return this.availableFields.links;
}
return false;
}
},
watch: {
value: function (value) {
if (null !== value) {
this.emitEvent = false;
this.links = lodashClonedeep(value);
}
},
links: function (value) {
if (true === this.emitEvent) {
this.$emit('set-field', {index: this.index, field: 'links', value: lodashClonedeep(value)});
}
this.emitEvent = true;
},
customFields: function (value) {
this.availableFields = value;
}
},
methods: {
removeLink: function (index) {
this.links.splice(index, 1);
},
getTextForLinkType: function (linkTypeId) {
let parts = linkTypeId.split('-');
for (let i in this.linkTypes) {
if (this.linkTypes.hasOwnProperty(i) && /^0$|^[1-9]\d*$/.test(i) && i <= 4294967294) {
let current = this.linkTypes[i];
if (parts[0] === current.id && parts[1] === current.direction) {
return current.type;
}
}
}
return 'text for #' + linkTypeId;
},
selectTransaction: function (event) {
for (let i in this.searchResults) {
if (this.searchResults.hasOwnProperty(i) && /^0$|^[1-9]\d*$/.test(i) && i <= 4294967294) {
let current = this.searchResults[i];
if (current.selected) {
this.addToSelected(current);
}
if (!current.selected) {
// remove from
this.removeFromSelected(current);
}
}
}
},
selectLinkType: function (event) {
for (let i in this.searchResults) {
if (this.searchResults.hasOwnProperty(i) && /^0$|^[1-9]\d*$/.test(i) && i <= 4294967294) {
let current = this.searchResults[i];
this.updateSelected(current.transaction_journal_id, current.link_type_id);
}
}
},
updateSelected(journalId, linkTypeId) {
for (let i in this.links) {
if (this.links.hasOwnProperty(i) && /^0$|^[1-9]\d*$/.test(i) && i <= 4294967294) {
let current = this.links[i];
if (parseInt(current.transaction_journal_id) === journalId) {
this.links[i].link_type_id = linkTypeId;
}
}
}
},
addToSelected(journal) {
let result = this.links.find(({transaction_journal_id}) => transaction_journal_id === journal.transaction_journal_id);
if (typeof result === 'undefined') {
this.links.push(journal);
}
},
removeFromSelected(journal) {
for (let i in this.links) {
if (this.links.hasOwnProperty(i) && /^0$|^[1-9]\d*$/.test(i) && i <= 4294967294) {
let current = this.links[i];
if (current.transaction_journal_id === journal.transaction_journal_id) {
this.links.splice(parseInt(i), 1);
}
}
}
},
getLinkTypes: function () {
let url = './api/v1/link_types';
axios.get(url)
.then(response => {
this.parseLinkTypes(response.data);
}
);
},
resetModal: function() {
this.search();
},
parseLinkTypes: function (data) {
for (let i in data.data) {
if (data.data.hasOwnProperty(i) && /^0$|^[1-9]\d*$/.test(i) && i <= 4294967294) {
let current = data.data[i];
let linkTypeInward = {
id: current.id,
type: current.attributes.inward,
direction: 'inward'
};
let linkTypeOutward = {
id: current.id,
type: current.attributes.outward,
direction: 'outward'
};
if (linkTypeInward.type === linkTypeOutward.type) {
linkTypeInward.type = linkTypeInward.type + ' (←)';
linkTypeOutward.type = linkTypeOutward.type + ' (→)';
}
this.linkTypes.push(linkTypeInward);
this.linkTypes.push(linkTypeOutward);
}
}
},
search: function () {
if('' === this.query) {
this.searchResults = [];
return;
}
this.searching = true;
this.searchResults = [];
let url = './api/v1/search/transactions?limit=10&query=' + this.query;
axios.get(url)
.then(response => {
this.parseSearch(response.data);
}
);
},
parseSearch: function (data) {
for (let i in data.data) {
if (data.data.hasOwnProperty(i) && /^0$|^[1-9]\d*$/.test(i) && i <= 4294967294) {
for (let ii in data.data[i].attributes.transactions) {
if (data.data[i].attributes.transactions.hasOwnProperty(ii) && /^0$|^[1-9]\d*$/.test(ii) && ii <= 4294967294) {
let current = data.data[i].attributes.transactions[ii];
current.transaction_group_id = parseInt(data.data[i].id);
current.selected = this.isJournalSelected(current.transaction_journal_id);
current.link_type_id = this.getJournalLinkType(current.transaction_journal_id);
current.link_type_text = '';
this.searchResults.push(current);
}
}
}
}
this.searching = false;
},
getJournalLinkType: function (journalId) {
for (let i in this.links) {
if (this.links.hasOwnProperty(i) && /^0$|^[1-9]\d*$/.test(i) && i <= 4294967294) {
let current = this.links[i];
if (current.transaction_journal_id === journalId) {
return current.link_type_id;
}
}
}
return '1-inward';
},
isJournalSelected: function (journalId) {
for (let i in this.links) {
if (this.links.hasOwnProperty(i) && /^0$|^[1-9]\d*$/.test(i) && i <= 4294967294) {
let current = this.links[i];
if (current.transaction_journal_id === journalId) {
return true;
}
}
}
return false;
}
}
}
</script>

View File

@@ -1,435 +0,0 @@
<!--
- TransactionListLarge.vue
- Copyright (c) 2020 james@firefly-iii.org
-
- This file is part of Firefly III (https://github.com/firefly-iii).
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<div>
<div class="row">
<div class="col-lg-8 col-md-6 col-sm-12 col-xs-12">
<BPagination v-if="!loading"
v-model="currentPage"
:total-rows="total"
:per-page="perPage"
aria-controls="my-table"
></BPagination>
</div>
<div class="col-lg-4 col-md-6 col-sm-12 col-xs-12">
<button @click="newCacheKey" class="btn btn-sm float-right btn-info"><span class="fas fa-sync"></span></button>
</div>
</div>
<div class="row">
<div class="col">
<div class="card">
<div class="card-body p-0">
<BTable id="my-table" small striped hover responsive="md" primary-key="key" :no-local-sorting="false"
:items="transactions"
:fields="fields"
:per-page="perPage"
sort-icon-left
ref="table"
:current-page="currentPage"
:busy.sync="loading"
:sort-desc.sync="sortDesc"
:sort-compare="tableSortCompare"
>
<template #table-busy>
<span class="fa fa-spinner fa-spin"></span>
</template>
<template #cell(type)="data">
<span v-if="!data.item.dummy">
<span class="fas fa-long-arrow-alt-right" v-if="'deposit' === data.item.type.toLowerCase()"></span>
<span class="fas fa-long-arrow-alt-left" v-if="'withdrawal' === data.item.type.toLowerCase()"></span>
<span class="fas fa-arrows-alt-h" v-if="'transfer' === data.item.type.toLowerCase()"></span>
</span>
</template>
<template #cell(description)="data">
<span class="fa fa-spinner fa-spin" v-if="data.item.dummy"></span>
<span v-if="!data.item.split">
<span v-if="data.item.hasAttachments" class="fas fa-paperclip"></span>
<a :href="'./transactions/show/' + data.item.id" :title="data.value">
{{ data.item.description }}
</a>
</span>
<span v-if="data.item.split">
<!-- title first -->
<span class="fas fa-angle-right" @click="toggleCollapse(data.item.id)" style="cursor: pointer;"></span>
<span v-if="data.item.hasAttachments" class="fas fa-paperclip"></span>
<a :href="'./transactions/show/' + data.item.id" :title="data.value">
{{ data.item.description }}
</a><br/>
<span v-if="!data.item.collapsed">
<span v-for="(split, index) in data.item.splits" v-bind:key="index">
&nbsp; &nbsp; {{ split.description }}<br/>
</span>
</span>
</span>
</template>
<template #cell(amount)="data">
<!-- row amount first (3x) -->
<span :class="'text-success ' + (!data.item.collapsed ? 'font-weight-bold' : '')" v-if="'deposit' === data.item.type">
{{ Intl.NumberFormat(locale, {style: 'currency', currency: data.item.currency_code}).format(data.item.amount) }}
</span>
<span :class="'text-danger ' + (!data.item.collapsed ? 'font-weight-bold' : '')" v-if="'withdrawal' === data.item.type">
{{ Intl.NumberFormat(locale, {style: 'currency', currency: data.item.currency_code}).format(-data.item.amount) }}
</span>
<span :class="'text-muted ' + (!data.item.collapsed ? 'font-weight-bold' : '')" v-if="'transfer' === data.item.type.toLowerCase()">
{{ Intl.NumberFormat(locale, {style: 'currency', currency: data.item.currency_code}).format(data.item.amount) }}
</span>
<br/>
<!-- splits -->
<span v-if="!data.item.collapsed">
<span v-for="(split, index) in data.item.splits" v-bind:key="index">
{{ Intl.NumberFormat(locale, {style: 'currency', currency: split.currency_code}).format(split.amount) }}<br/>
</span>
</span>
</template>
<template #cell(date)="data">
{{ data.item.date_formatted }}
</template>
<template #cell(source_account)="data">
<!-- extra break for splits -->
<span v-if="true===data.item.split && !data.item.collapsed">
<br/>
</span>
<em v-if="true===data.item.split && data.item.collapsed">
...
</em>
<!-- loop all accounts, hidden if split -->
<span v-for="(split, index) in data.item.splits" v-bind:key="index"
v-if="false===data.item.split || (true===data.item.split && !data.item.collapsed)">
<a :href="'./accounts/show/' + split.source_id" :title="split.source_name">{{ split.source_name }}</a><br/>
</span>
</template>
<template #cell(destination_account)="data">
<!-- extra break for splits -->
<span v-if="true===data.item.split && !data.item.collapsed">
<br/>
</span>
<em v-if="true===data.item.split && data.item.collapsed">
...
</em>
<!-- loop all accounts, hidden if split -->
<span v-for="(split, index) in data.item.splits" v-bind:key="index"
v-if="false===data.item.split || (true===data.item.split && !data.item.collapsed)">
<a :href="'./accounts/show/' + split.destination_id" :title="split.destination_name">{{ split.destination_name }}</a><br/>
</span>
</template>
<template #cell(menu)="data">
<div class="btn-group btn-group-sm">
<div class="dropdown">
<button class="btn btn-light btn-sm dropdown-toggle" type="button" :id="'dropdownMenuButton' + data.item.id" data-toggle="dropdown"
aria-haspopup="true" aria-expanded="false">
{{ $t('firefly.actions') }}
</button>
<div class="dropdown-menu" :aria-labelledby="'dropdownMenuButton' + data.item.id">
<a class="dropdown-item" :href="'./transactions/edit/' + data.item.id"><span class="fa fas fa-pencil-alt"></span> {{
$t('firefly.edit')
}}</a>
<a class="dropdown-item" :href="'./transactions/delete/' + data.item.id"><span class="fa far fa-trash"></span> {{
$t('firefly.delete')
}}</a>
</div>
</div>
</div>
</template>
<template #cell(category_name)="data">
<!-- extra break for splits -->
<span v-if="true===data.item.split && !data.item.collapsed">
<br/>
</span>
<em v-if="true===data.item.split && data.item.collapsed">
...
</em>
<!-- loop all categories, hidden if split -->
<span v-for="(split, index) in data.item.splits" v-bind:key="index"
v-if="false===data.item.split || (true===data.item.split && !data.item.collapsed)">
<a :href="'./categories/show/' + split.category_id" :title="split.category_name">{{ split.category_name }}</a><br/>
</span>
</template>
</BTable>
</div>
<div class="card-footer"> (button)
<a :href="'./transactions/create/TODO'" class="btn btn-success"
:title="$t('firefly.create_new_transaction')">{{ $t('firefly.create_new_transaction') }}</a>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-8 col-md-6 col-sm-12 col-xs-12">
<BPagination
v-model="currentPage"
:total-rows="total"
:per-page="perPage"
aria-controls="my-table"
></BPagination>
</div>
<div class="col-lg-4 col-md-6 col-sm-12 col-xs-12">
<button @click="newCacheKey" class="btn btn-sm float-right btn-info"><span class="fas fa-sync"></span></button>
</div>
</div>
</div>
</template>
<script>
import {mapGetters, mapMutations} from "vuex";
import {BPagination, BTable} from 'bootstrap-vue';
import format from "date-fns/format";
export default {
name: "TransactionListLarge",
components: {BPagination, BTable},
data() {
return {
locale: 'en-US',
fields: [],
currentPage: 1,
transactions: [],
loading: true
}
},
computed: {
...mapGetters('root', ['listPageSize', 'cacheKey']),
},
created() {
this.locale = localStorage.locale ?? 'en-US';
this.updateFieldList();
//this.currentPage = this.page;
this.parseTransactions();
},
watch: {
currentPage: function (value) {
// console.log('Watch currentPage go to ' + value);
this.$emit('jump-page', {page: value});
},
entries: function (value) {
// console.log('detected new transactions! (' + value.length + ')');
this.parseTransactions();
if(this.isEmpty) {
this.loading=false;
}
},
// value: function (value) {
// // console.log('Watch value!');
// }
},
methods: {
...mapMutations('root', ['refreshCacheKey',]),
parseTransactions: function () {
this.transactions = [];
// console.log('Start of parseTransactions. Count of entries is ' + this.entries.length + ' and page is ' + this.page);
// console.log('Reported total is ' + this.total);
if (0 === this.entries.length) {
// console.log('Will not render now because length is 0.');
return;
}
// console.log('Now have ' + this.transactions.length + ' transactions');
for (let i = 0; i < this.total; i++) {
this.transactions.push({dummy: true, type: 'x'});
// console.log('Push dummy to index ' + i);
// console.log('Now have ' + this.transactions.length + ' transactions');
}
// console.log('Generated ' + this.total + ' dummies');
// console.log('Now have ' + this.transactions.length + ' transactions');
let index = (this.page - 1) * this.perPage;
// console.log('Start index is ' + index);
for (let i in this.entries) {
let transaction = this.entries[i];
// build split
this.transactions[index] = this.parseTransaction(transaction);
// console.log('Push transaction to index ' + index);
// console.log('Now have ' + this.transactions.length + ' transactions');
index++;
}
// console.log('Added ' + this.entries.length + ' entries');
// console.log('Now have ' + this.transactions.length + ' transactions');
// console.log(this.transactions);
this.loading = false;
},
newCacheKey: function () {
this.refreshCacheKey();
// console.log('Cache key is now ' + this.cacheKey);
this.$emit('refreshed-cache-key');
},
updateFieldList: function () {
this.fields = [
{key: 'type', label: ' ', sortable: false},
{key: 'description', label: this.$t('list.description'), sortable: true},
{key: 'amount', label: this.$t('list.amount'), sortable: true},
{key: 'date', label: this.$t('list.date'), sortable: true},
{key: 'source_account', label: this.$t('list.source_account'), sortable: true},
{key: 'destination_account', label: this.$t('list.destination_account'), sortable: true},
{key: 'category_name', label: this.$t('list.category'), sortable: true},
{key: 'menu', label: ' ', sortable: false},
];
},
/**
* Parse a single transaction.
* @param transaction
*/
parseTransaction: function (transaction) {
let row = {};
// default values:
row.splits = [];
row.key = transaction.id;
row.id = transaction.id
row.dummy = false;
row.hasAttachments = false;
// pick this up from the first transaction
let first = transaction.attributes.transactions[0];
row.type = first.type;
row.date = new Date(first.date);
row.date_formatted = format(row.date, this.$t('config.month_and_day_fns'));
row.description = first.description;
row.collapsed = true;
row.split = false;
row.amount = 0;
row.currency_code = first.currency_code;
if (transaction.attributes.transactions.length > 1) {
row.split = true;
row.description = transaction.attributes.group_title;
}
// collapsed?
if (typeof transaction.collapsed !== 'undefined') {
row.collapsed = transaction.collapsed;
}
//console.log('is collapsed? ' + row.collapsed);
// then loop each split
for (let i in transaction.attributes.transactions) {
if (transaction.attributes.transactions.hasOwnProperty(i)) {
let info = transaction.attributes.transactions[i];
let split = {};
row.amount = row.amount + parseFloat(info.amount);
split.type = info.type;
split.description = info.description;
split.amount = info.amount;
split.currency_code = info.currency_code;
split.source_name = info.source_name;
split.source_id = info.source_id;
split.destination_name = info.destination_name;
split.destination_id = info.destination_id;
split.category_id = info.category_id;
split.category_name = info.category_name;
split.split_index = i;
if(true === info.has_attachments) {
row.hasAttachments = true;
}
row.splits.push(split);
}
}
return row;
},
toggleCollapse: function (id) {
let transaction = this.transactions.filter(transaction => transaction.id === id)[0];
transaction.collapsed = !transaction.collapsed;
},
tableSortCompare: function (aRow, bRow, key, sortDesc, formatter, compareOptions, compareLocale) {
let a = aRow[key]
let b = bRow[key]
if (aRow.id === bRow.id) {
// Order split transactions normally when compared to each other, except always put the header first
if (aRow.split_parent === null) {
return sortDesc ? 1 : -1;
} else if (bRow.split_parent === null) {
return sortDesc ? -1 : 1;
}
} else {
// Sort split transactions based on their parent when compared to other transactions
if (aRow.split && aRow.split_parent !== null) {
a = aRow.split_parent[key]
}
if (bRow.split && bRow.split_parent !== null) {
b = bRow.split_parent[key]
}
}
if (
(typeof a === 'number' && typeof b === 'number') ||
(a instanceof Date && b instanceof Date)
) {
// If both compared fields are native numbers or both are native dates
return a < b ? -1 : a > b ? 1 : 0
} else {
// Otherwise stringify the field data and use String.prototype.localeCompare
return toString(a).localeCompare(toString(b), compareLocale, compareOptions)
}
function toString(value) {
if (value === null || typeof value === 'undefined') {
return ''
} else if (value instanceof Object) {
return Object.keys(value)
.sort()
.map(key => toString(value[key]))
.join(' ')
} else {
return String(value)
}
}
},
},
props: {
page: {
type: Number
},
perPage: {
type: Number,
default: 1
},
sortDesc: {
type: Boolean,
default: true
},
isEmpty: {
type: Boolean,
default: false
},
total: {
type: Number,
default: 1
},
entries: {
type: Array,
default: function () {
return [];
}
},
accountId: {
type: Number,
default: function () {
return 0;
}
},
}
}
</script>

View File

@@ -1,168 +0,0 @@
<!--
- TransactionLocation.vue
- 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/>.
-->
<template>
<div v-if="showField" class="form-group">
<div class="text-xs d-none d-lg-block d-xl-block">
{{ $t('firefly.location') }}
</div>
<div style="width:100%;height:300px;">
<l-map
ref="myMap"
:center="center"
:zoom="zoom" style="width:100%;height:300px;"
@ready="prepMap()"
@update:zoom="zoomUpdated"
@update:center="centerUpdated"
@update:bounds="boundsUpdated"
>
<l-tile-layer :url="url"></l-tile-layer>
<l-marker :lat-lng="marker" :visible="hasMarker"></l-marker>
</l-map>
<span>
<button class="btn btn-default btn-xs" @click="clearLocation">{{ $t('firefly.clear_location') }}</button>
</span>
</div>
<p>&nbsp;</p>
</div>
</template>
<script>
// If you need to reference 'L', such as in 'L.icon', then be sure to
// explicitly import 'leaflet' into your component
// import L from 'leaflet';
import {LMap, LMarker, LTileLayer} from 'vue2-leaflet';
import 'leaflet/dist/leaflet.css';
import L from 'leaflet';
delete L.Icon.Default.prototype._getIconUrl;
L.Icon.Default.mergeOptions({
iconRetinaUrl: require('leaflet/dist/images/marker-icon-2x.png'),
iconUrl: require('leaflet/dist/images/marker-icon.png'),
shadowUrl: require('leaflet/dist/images/marker-shadow.png')
});
export default {
name: "TransactionLocation",
props: {
index: {},
value: {
type: Object,
required: false
},
errors: {},
customFields: {},
},
components: {
LMap,
LTileLayer,
LMarker,
},
created() {
if (null === this.value || typeof this.value === 'undefined') {
axios.get('./api/v1/configuration/firefly.default_location').then(response => {
this.zoom = parseInt(response.data.data.value.zoom_level);
this.center =
[
parseFloat(response.data.data.value.latitude),
parseFloat(response.data.data.value.longitude),
]
;
});
return;
}
if (null !== this.value.zoom_level && null !== this.value.latitude && null !== this.value.longitude) {
this.zoom = this.value.zoom_level;
this.center = [
parseFloat(this.value.latitude),
parseFloat(this.value.longitude),
];
this.hasMarker = true;
}
},
data() {
return {
availableFields: this.customFields,
url: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
zoom: 3,
center: [0, 0],
bounds: null,
map: null,
hasMarker: false,
marker: [0, 0],
}
},
methods: {
prepMap: function () {
this.map = this.$refs.myMap.mapObject;
this.map.on('contextmenu', this.setObjectLocation);
this.map.on('zoomend', this.saveZoomLevel);
},
setObjectLocation: function (event) {
this.marker = [event.latlng.lat, event.latlng.lng];
this.hasMarker = true;
this.emitEvent();
},
saveZoomLevel: function () {
this.emitEvent();
},
clearLocation: function () {
this.hasMarker = false;
this.emitEvent();
},
emitEvent() {
this.$emit('set-marker-location', {
index: this.index,
zoomLevel: this.zoom,
lat: this.marker[0],
lng: this.marker[1],
hasMarker: this.hasMarker
}
);
},
zoomUpdated(zoom) {
this.zoom = zoom;
},
centerUpdated(center) {
this.center = center;
},
boundsUpdated(bounds) {
this.bounds = bounds;
}
},
computed: {
showField: function () {
if ('location' in this.availableFields) {
return this.availableFields.location;
}
return false;
}
},
watch: {
customFields: function (value) {
this.availableFields = value;
},
}
}
</script>

View File

@@ -1,69 +0,0 @@
<!--
- TransactionNotes.vue
- 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/>.
-->
<template>
<div v-if="showField" class="form-group">
<div class="text-xs d-none d-lg-block d-xl-block">
{{ $t('firefly.notes') }}
</div>
<div class="input-group">
<textarea
v-model="notes"
:class="errors.length > 0 ? 'form-control is-invalid' : 'form-control'"
:placeholder="$t('firefly.notes')"
></textarea>
</div>
</div>
</template>
<script>
export default {
props: ['index', 'value', 'errors', 'customFields'],
name: "TransactionNotes",
data() {
return {
notes: this.value,
availableFields: this.customFields,
}
},
computed: {
showField: function () {
if ('notes' in this.availableFields) {
return this.availableFields.notes;
}
return false;
}
},
watch: {
value: function (value) {
this.notes = value;
},
customFields: function (value) {
this.availableFields = value;
},
notes: function (value) {
this.$emit('set-field', {field: 'notes', index: this.index, value: value});
}
}
}
</script>

View File

@@ -1,100 +0,0 @@
<!--
- TransactionPiggyBank.vue
- 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/>.
-->
<template>
<div class="form-group">
<div class="text-xs d-none d-lg-block d-xl-block">
{{ $t('firefly.piggy_bank') }}
</div>
<div class="input-group">
<select
ref="piggy_bank_id"
v-model="piggy_bank_id"
:class="errors.length > 0 ? 'form-control is-invalid' : 'form-control'"
:title="$t('firefly.piggy_bank')"
autocomplete="off"
name="piggy_bank_id[]"
>
<option v-for="piggy in this.piggyList" :label="piggy.name_with_balance" :value="piggy.id">{{ piggy.name_with_balance }}</option>
</select>
</div>
<span v-if="errors.length > 0">
<span v-for="error in errors" class="text-danger small">{{ error }}<br/></span>
</span>
</div>
</template>
<script>
export default {
props: ['index', 'value', 'errors'],
name: "TransactionPiggyBank",
data() {
return {
piggyList: [],
piggy_bank_id: this.value,
}
},
created() {
this.collectData();
},
methods: {
collectData() {
this.piggyList.push(
{
id: 0,
name_with_balance: this.$t('firefly.no_piggy_bank'),
}
);
this.getPiggies();
},
getPiggies() {
axios.get('./api/v1/autocomplete/piggy-banks-with-balance')
.then(response => {
this.parsePiggies(response.data);
}
);
},
parsePiggies(data) {
for (let key in data) {
if (data.hasOwnProperty(key) && /^0$|^[1-9]\d*$/.test(key) && key <= 4294967294) {
let current = data[key];
this.piggyList.push(
{
id: parseInt(current.id),
name_with_balance: current.name_with_balance
}
);
}
}
},
},
watch: {
value: function (value) {
this.piggy_bank_id = value;
},
piggy_bank_id: function (value) {
this.$emit('set-field', {field: 'piggy_bank_id', index: this.index, value: value});
this.emitEvent = true;
}
}
}
</script>

View File

@@ -1,136 +0,0 @@
<!--
- TransactionTags.vue
- 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/>.
-->
<template>
<div class="form-group">
<div class="text-xs d-none d-lg-block d-xl-block">
{{ $t('firefly.tags') }}
</div>
<div class="input-group">
<vue-tags-input
v-model="currentTag"
:add-only-from-autocomplete="false"
:autocomplete-items="autocompleteItems"
:tags="tags"
ref="input"
:title="$t('firefly.tags')"
v-bind:placeholder="$t('firefly.tags')"
@tags-changed="newTags => this.tags = newTags"
/>
</div>
<span v-if="errors.length > 0">
<span v-for="error in errors" class="text-danger small">{{ error }}<br/></span>
</span>
</div>
</template>
<script>
import VueTagsInput from "@johmun/vue-tags-input";
import axios from "axios";
export default {
name: "TransactionTags",
components: {
VueTagsInput
},
props: ['value', 'index', 'errors'],
data() {
return {
autocompleteItems: [],
debounce: null,
tags: [],
currentTag: '',
updateTags: true, // the idea is that this is always true, except when the tags-function sets the value.
tagList: this.value,
};
},
created() {
let tags = [];
for (let i in this.value) {
if (this.value.hasOwnProperty(i) && /^0$|^[1-9]\d*$/.test(i) && i <= 4294967294) {
tags.push({text: this.value[i]});
}
}
this.updateTags = false;
this.tags = tags;
},
watch: {
'currentTag': 'initItems',
value: function (value) {
this.tagList = value;
},
tagList: function (value) {
// console.log('watch tagList');
this.$emit('set-field', {field: 'tags', index: this.index, value: value});
this.updateTags = false;
this.tags = value;
},
tags: function (value) {
if (this.updateTags) {
let shortList = [];
for (let key in value) {
if (value.hasOwnProperty(key)) {
shortList.push({text: value[key].text});
}
}
this.tagList = shortList;
}
this.updateTags = true;
}
},
methods: {
initItems() {
if (this.currentTag.length < 2) {
return;
}
const url = document.getElementsByTagName('base')[0].href + `api/v1/autocomplete/tags?query=${this.currentTag}`;
clearTimeout(this.debounce);
this.debounce = setTimeout(() => {
axios.get(url).then(response => {
this.autocompleteItems = response.data.map(item => {
return {text: item.tag};
});
}).catch(() => console.warn('Oh. Something went wrong loading tags.'));
}, 300);
},
},
}
</script>
<style>
.vue-tags-input {
width: 100%;
max-width: 100% !important;
display: block;
border-radius: 0.25rem;
}
.ti-input {
border-radius: 0.25rem;
max-width: 100%;
width: 100%;
}
.ti-new-tag-input {
font-size: 1rem;
}
</style>