Rebuild new layout.

This commit is contained in:
James Cole
2021-04-08 11:21:20 +02:00
parent 6160f99e92
commit 56dff7ea67
70 changed files with 2135 additions and 772 deletions

View File

@@ -17,9 +17,6 @@
* 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/>.
*/
// Firefly III colors (?)
$blue: #1E6581;
$green: #64B624;
$red: #CD5029;

View File

@@ -0,0 +1,95 @@
<!--
- 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">
<i class="fas fa-spinner fa-spin"></i>
</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>
<style scoped>
</style>

View File

@@ -0,0 +1,330 @@
<!--
- 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">
<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)"/>
<Currency :disabled="submitting" v-model="currency_id" :errors="errors.currency" 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)"/>
<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)"/>
<GenericAttachments :disabled="submitting" :title="$t('form.attachments')" field-name="attachments" :errors="errors.attachments"/>
</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>
const lodashClonedeep = require('lodash.clonedeep');
import Currency from "./Currency";
import AssetAccountRole from "./AssetAccountRole"
import LiabilityType from "./LiabilityType";
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: {
Currency, AssetAccountRole, LiabilityType, LiabilityDirection, Interest, InterestPeriod,
GenericTextInput, GenericTextarea, GenericLocation, GenericAttachments, GenericCheckbox, Alert
},
created() {
this.errors = lodashClonedeep(this.defaultErrors);
let pathName = window.location.pathname;
let parts = pathName.split('/');
this.type = parts[parts.length - 1];
},
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: {},
account_role: 'defaultAsset',
errors: {},
defaultErrors: {
name: [],
currency: [],
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: [],
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;
},
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 => {
console.log('success!');
this.returnedId = parseInt(response.data.data.id);
this.returnedTitle = response.data.data.attributes.name;
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 = {};
}
})
.catch(error => {
this.submitting = false;
this.parseErrors(error.response.data);
});
},
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,
"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.opening_balance = this.liability_amount;
submission.opening_balance_date = this.liability_date;
}
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 ('asset' === this.type && 'ccAsset' === this.account_role) {
submission.credit_card_type = 'monthlyFull';
submission.monthly_payment_date = '2021-01-01';
}
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>
<style scoped>
</style>

View File

@@ -0,0 +1,115 @@
<!--
- 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">
<i class="fas fa-spinner fa-spin"></i>
</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">{{ 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: "Currency",
props: {
value: {},
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.currency_id || typeof this.currency_id === 'undefined')) {
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});
},
},
created() {
this.loadCurrencies()
}
}
</script>
<style scoped>
</style>

View File

@@ -47,7 +47,7 @@
:sort-desc.sync="sortDesc"
>
<template #cell(title)="data">
<a :href="'./accounts/show/' + data.item.id" :title="data.value">{{ data.value }}</a>
<a :class="false === data.item.active ? 'text-muted' : ''" :href="'./accounts/show/' + data.item.id" :title="data.value">{{ data.value }}</a>
</template>
<template #cell(number)="data">
<span v-if="null !== data.item.iban && null === data.item.account_number">{{ data.item.iban }}</span>

View File

@@ -0,0 +1,73 @@
<!--
- 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"><i class="far fa-trash-alt"></i></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>
<style scoped>
</style>

View File

@@ -0,0 +1,96 @@
<!--
- 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">
<i class="fas fa-spinner fa-spin"></i>
</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>
<style scoped>
</style>

View File

@@ -0,0 +1,71 @@
<!--
- 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"><i class="far fa-trash-alt"></i></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>
<style scoped>
</style>

View File

@@ -0,0 +1,71 @@
<!--
- 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"><i class="far fa-trash-alt"></i></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>
<style scoped>
</style>

View File

@@ -0,0 +1,76 @@
<!--
- 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});
},
},
created() {
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,96 @@
<!--
- 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">
<i class="fas fa-spinner fa-spin"></i>
</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>
<style scoped>
</style>

View File

@@ -0,0 +1,82 @@
<!--
- 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
/>
</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 [];
}
},
},
methods: {
selectedFile: function() {
this.$emit('selected-attachments');
},
},
data() {
return {
localValue: this.value
}
},
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,86 @@
<!--
- 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>
<style scoped>
</style>

View File

@@ -0,0 +1,190 @@
<!--
- 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>
<style scoped>
</style>

View File

@@ -0,0 +1,99 @@
<!--
- 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"
:type="fieldType"
:disabled=disabled
:step="fieldStep"
/>
<div class="input-group-append">
<button class="btn btn-outline-secondary" tabindex="-1" type="button"><i class="far fa-trash-alt"></i></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
}
},
watch: {
localValue: function (value) {
this.$emit('set-field', {field: this.fieldName, value: value});
},
value: function(value) {
this.localValue = value;
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,83 @@
<!--
- 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});
},
}
}
</script>
<style scoped>
</style>

View File

@@ -20,7 +20,7 @@
const lodashClonedeep = require('lodash.clonedeep');
import {getDefaultTransaction, getDefaultErrors} from '../../../shared/transactions';
import {getDefaultTransaction, getDefaultErrors} from '../../../../shared/transactions';
// initial state
const state = () => ({

View File

@@ -22,94 +22,96 @@
<div>
<alert :message="errorMessage" type="danger"/>
<alert :message="successMessage" type="success"/>
<SplitPills :transactions="transactions"/>
<div class="tab-content">
<SplitForm
v-for="(transaction, index) in this.transactions"
v-bind:key="index"
:allowed-opposing-types="allowedOpposingTypes"
:count="transactions.length"
:custom-fields="customFields"
:date="date"
:destination-allowed-types="destinationAllowedTypes"
:index="index"
:source-allowed-types="sourceAllowedTypes"
:submitted-transaction="submittedTransaction"
:time="time"
:transaction="transaction"
:transaction-type="transactionType"
v-on:uploaded-attachments="uploadedAttachment($event)"
v-on:set-marker-location="storeLocation($event)"
v-on:set-account="storeAccountValue($event)"
v-on:switch-accounts="switchAccounts($event)"
v-on:set-date="storeDate($event)"
v-on:set-time="storeTime($event)"
v-on:set-field="storeField($event)"
v-on:remove-transaction="removeTransaction($event)"
v-on:set-dest-types="setDestinationAllowedTypes($event)"
v-on:set-src-types="setSourceAllowedTypes($event)"
<form @submit="submitTransaction">
<SplitPills :transactions="transactions"/>
<div class="tab-content">
<!-- v-on:switch-accounts="switchAccounts($event)" -->
<!-- :allowed-opposing-types="allowedOpposingTypes" -->
<SplitForm
v-for="(transaction, index) in this.transactions"
v-bind:key="index"
:count="transactions.length"
:custom-fields="customFields"
:date="date"
:destination-allowed-types="destinationAllowedTypes"
:index="index"
:source-allowed-types="sourceAllowedTypes"
:submitted-transaction="submittedTransaction"
:time="time"
:transaction="transaction"
:transaction-type="transactionType"
v-on:uploaded-attachments="uploadedAttachment($event)"
v-on:set-marker-location="storeLocation($event)"
v-on:set-account="storeAccountValue($event)"
v-on:set-date="storeDate($event)"
v-on:set-time="storeTime($event)"
v-on:set-field="storeField($event)"
v-on:remove-transaction="removeTransaction($event)"
v-on:set-dest-types="setDestinationAllowedTypes($event)"
v-on:set-src-types="setSourceAllowedTypes($event)"
/>
</div>
/>
</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 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>
<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">
<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 class="btn btn-outline-primary btn-block" @click="addTransaction"><i class="far fa-clone"></i> {{ $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"><i class="far fa-save"></i> {{ $t('firefly.store_transaction') }}</span>
<span v-if="!enableSubmit"><i class="fas fa-spinner fa-spin"></i></span>
</button>
</div>
</div>
<div class="row">
<div class="col">
&nbsp;
</div>
<button class="btn btn-outline-primary btn-block" @click="addTransaction"><i class="far fa-clone"></i> {{ $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"><i class="far fa-save"></i> {{ $t('firefly.store_transaction') }}</span>
<span v-if="!enableSubmit"><i class="fas fa-spinner fa-spin"></i></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 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>
</div>
</form>
</div>
</template>
@@ -119,7 +121,7 @@ import Alert from '../partials/Alert';
import SplitPills from "./SplitPills";
import TransactionGroupTitle from "./TransactionGroupTitle";
import SplitForm from "./SplitForm";
import {toW3CString} from '../shared/transactions';
import {toW3CString} from '../../shared/transactions';
const {mapState, mapGetters, mapActions, mapMutations} = createNamespacedHelpers('transactions/create')
@@ -135,7 +137,15 @@ export default {
* Grab some stuff from the API, add the first transaction.
*/
created() {
this.getAllowedOpposingTypes();
// set transaction type:
let pathName = window.location.pathname;
let parts = pathName.split('/');
let type = parts[parts.length - 1];
console.log('Set transaction type to ' + type);
this.setTransactionType(type[0].toUpperCase() + type.substring(1));
//this.getAllowedOpposingTypes();
this.getExpectedSourceTypes();
this.getAccountToTransaction();
this.getCustomFields();
this.addTransaction();
@@ -291,7 +301,8 @@ export default {
* 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 () {
submitTransaction: function (event) {
event.preventDefault();
// console.log('submitTransaction()');
// disable the submit button:
this.enableSubmit = false;
@@ -398,7 +409,7 @@ export default {
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);
//this.calculateTransactionType(payload.index);
},
storeField: function (payload) {
this.updateField(payload);
@@ -414,60 +425,60 @@ export default {
this.setGroupTitle({groupTitle: value});
},
/**
* Calculate the transaction type based on what's currently in the store.
*/
calculateTransactionType: function (index) {
//console.log('calculateTransactionType(' + index + ')');
if (0 === index) {
let source = this.transactions[0].source_account_type;
let dest = this.transactions[0].destination_account_type;
if (null === source || null === dest) {
//console.log('transactionType any');
this.setTransactionType('any');
//this.$store.commit('setTransactionType', 'any');
//console.log('calculateTransactionType: either type is NULL so no dice.');
return;
}
if ('' === source || '' === dest) {
//console.log('transactionType any');
this.setTransactionType('any');
//this.$store.commit('setTransactionType', 'any');
//console.log('calculateTransactionType: either type is empty so no dice.');
return;
}
// ok so type is set on both:
let expectedDestinationTypes = this.accountToTransaction[source];
if ('undefined' !== typeof expectedDestinationTypes) {
let transactionType = expectedDestinationTypes[dest];
if ('undefined' !== typeof expectedDestinationTypes[dest]) {
//console.log('Found a type: ' + transactionType);
this.setTransactionType(transactionType);
//this.$store.commit('setTransactionType', transactionType);
//console.log('calculateTransactionType: ' + source + ' --> ' + dest + ' = ' + transactionType);
return;
}
}
//console.log('Found no type for ' + source + ' --> ' + dest);
if ('Asset account' !== source) {
//console.log('Drop ID from destination.');
this.updateField({index: 0, field: 'destination_account_id', value: null});
//console.log('calculateTransactionType: drop ID from destination.');
// source.id =null
// context.commit('updateField', {field: 'source_account',index: })
// context.state.transactions[0].source_account.id = null;
}
if ('Asset account' !== dest) {
//console.log('Drop ID from source.');
this.updateField({index: 0, field: 'source_account_id', value: null});
//console.log('calculateTransactionType: drop ID from source.');
//context.state.transactions[0].destination_account.id = null;
}
//console.log('calculateTransactionType: fallback, type to any.');
this.setTransactionType('any');
//this.$store.commit('setTransactionType', 'any');
}
},
// /**
// * Calculate the transaction type based on what's currently in the store.
// */
// calculateTransactionType: function (index) {
// //console.log('calculateTransactionType(' + index + ')');
// if (0 === index) {
// let source = this.transactions[0].source_account_type;
// let dest = this.transactions[0].destination_account_type;
// if (null === source || null === dest) {
// //console.log('transactionType any');
// this.setTransactionType('any');
// //this.$store.commit('setTransactionType', 'any');
// //console.log('calculateTransactionType: either type is NULL so no dice.');
// return;
// }
// if ('' === source || '' === dest) {
// //console.log('transactionType any');
// this.setTransactionType('any');
// //this.$store.commit('setTransactionType', 'any');
// //console.log('calculateTransactionType: either type is empty so no dice.');
// return;
// }
// // ok so type is set on both:
// let expectedDestinationTypes = this.accountToTransaction[source];
// if ('undefined' !== typeof expectedDestinationTypes) {
// let transactionType = expectedDestinationTypes[dest];
// if ('undefined' !== typeof expectedDestinationTypes[dest]) {
// //console.log('Found a type: ' + transactionType);
// this.setTransactionType(transactionType);
// //this.$store.commit('setTransactionType', transactionType);
// //console.log('calculateTransactionType: ' + source + ' --> ' + dest + ' = ' + transactionType);
// return;
// }
// }
// //console.log('Found no type for ' + source + ' --> ' + dest);
// if ('Asset account' !== source) {
// //console.log('Drop ID from destination.');
// this.updateField({index: 0, field: 'destination_account_id', value: null});
// //console.log('calculateTransactionType: drop ID from destination.');
// // source.id =null
// // context.commit('updateField', {field: 'source_account',index: })
// // context.state.transactions[0].source_account.id = null;
// }
// if ('Asset account' !== dest) {
// //console.log('Drop ID from source.');
// this.updateField({index: 0, field: 'source_account_id', value: null});
// //console.log('calculateTransactionType: drop ID from source.');
// //context.state.transactions[0].destination_account.id = null;
// }
// //console.log('calculateTransactionType: fallback, type to any.');
// this.setTransactionType('any');
// //this.$store.commit('setTransactionType', 'any');
// }
// },
/**
* Submit transaction links.
*/
@@ -669,25 +680,25 @@ export default {
},
switchAccounts: function (index) {
// console.log('user wants to switch Accounts');
let origSourceId = this.transactions[index].source_account_id;
let origSourceName = this.transactions[index].source_account_name;
let origSourceType = this.transactions[index].source_account_type;
let origDestId = this.transactions[index].destination_account_id;
let origDestName = this.transactions[index].destination_account_name;
let origDestType = this.transactions[index].destination_account_type;
this.updateField({index: 0, field: 'source_account_id', value: origDestId});
this.updateField({index: 0, field: 'source_account_name', value: origDestName});
this.updateField({index: 0, field: 'source_account_type', value: origDestType});
this.updateField({index: 0, field: 'destination_account_id', value: origSourceId});
this.updateField({index: 0, field: 'destination_account_name', value: origSourceName});
this.updateField({index: 0, field: 'destination_account_type', value: origSourceType});
this.calculateTransactionType(0);
},
// switchAccounts: function (index) {
// // console.log('user wants to switch Accounts');
// let origSourceId = this.transactions[index].source_account_id;
// let origSourceName = this.transactions[index].source_account_name;
// let origSourceType = this.transactions[index].source_account_type;
//
// let origDestId = this.transactions[index].destination_account_id;
// let origDestName = this.transactions[index].destination_account_name;
// let origDestType = this.transactions[index].destination_account_type;
//
// this.updateField({index: 0, field: 'source_account_id', value: origDestId});
// this.updateField({index: 0, field: 'source_account_name', value: origDestName});
// this.updateField({index: 0, field: 'source_account_type', value: origDestType});
//
// this.updateField({index: 0, field: 'destination_account_id', value: origSourceId});
// this.updateField({index: 0, field: 'destination_account_name', value: origSourceName});
// this.updateField({index: 0, field: 'destination_account_type', value: origSourceType});
// this.calculateTransactionType(0);
// },
/**
@@ -703,19 +714,34 @@ export default {
) {
let theDate = new Date(this.date);
// update time in date object.
theDate.setHours(this.time.getHours());
theDate.setMinutes(this.time.getMinutes());
theDate.setSeconds(this.time.getSeconds());
//theDate.setHours(this.time.getHours());
//theDate.setMinutes(this.time.getMinutes());
//theDate.setSeconds(this.time.getSeconds());
dateStr = toW3CString(theDate);
}
console.log('Date is now ' + dateStr);
console.log(dateStr);
// console.log('dateStr = ' + dateStr);
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: dateStr,
type: this.transactionType,
type: this.transactionType.toLowerCase(),
// account
source_id: array.source_account_id ?? null,
@@ -761,10 +787,10 @@ export default {
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) {
if (typeof current === 'object' && null !== current) {
currentSplit.tags.push(current.text);
}
if(typeof current === 'string') {
if (typeof current === 'string') {
currentSplit.tags.push(current);
}
}
@@ -788,17 +814,17 @@ export default {
}
// do transaction type
let transactionType;
let firstSource;
let firstDestination;
// let transactionType;
// let firstSource;
// let firstDestination;
// get transaction type from first transaction
transactionType = this.transactionType ? this.transactionType.toLowerCase() : 'any';
//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;
//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);
@@ -806,15 +832,15 @@ export default {
// 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 ('any' === transactionType && ['asset', 'Asset account', 'Loan', 'Debt', 'Mortgage'].includes(firstSource)) {
// transactionType = 'withdrawal';
// }
if ('any' === transactionType && ['asset', 'Asset account', 'Loan', 'Debt', 'Mortgage'].includes(firstDestination)) {
transactionType = 'deposit';
if ('Deposit' === this.transactionType) {
// transactionType = 'deposit';
currentSplit.currency_id = array.destination_account_currency_id;
}
currentSplit.type = transactionType;
//currentSplit.type = transactionType;
//console.log('Final type is ' + transactionType);
let links = [];
@@ -833,6 +859,21 @@ export default {
}
}
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;
@@ -843,9 +884,21 @@ export default {
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];
//this.allowedOpposingTypes = response.data.data.value;
});
},
/**
* Get API value.
*/

View File

@@ -23,88 +23,90 @@
<Alert :message="errorMessage" type="danger"/>
<Alert :message="successMessage" type="success"/>
<Alert :message="warningMessage" type="warning"/>
<SplitPills :transactions="transactions"/>
<form @submit="submitTransaction">
<SplitPills :transactions="transactions"/>
<div class="tab-content">
<SplitForm
v-for="(transaction, index) in this.transactions"
v-bind:key="index"
:count="transactions.length"
:transaction="transaction"
:allowed-opposing-types="allowedOpposingTypes"
:custom-fields="customFields"
:date="date"
:time="time"
:index="index"
:transaction-type="transactionType"
:destination-allowed-types="destinationAllowedTypes"
:source-allowed-types="sourceAllowedTypes"
:allow-switch="false"
:submitted-transaction="submittedTransaction"
v-on:uploaded-attachments="uploadedAttachment($event)"
v-on:set-marker-location="storeLocation($event)"
v-on:set-account="storeAccountValue($event)"
v-on:set-date="storeDate($event)"
v-on:set-time="storeTime($event)"
v-on:set-field="storeField($event)"
v-on:remove-transaction="removeTransaction($event)"
v-on:selected-attachments="selectedAttachments($event)"
/>
</div>
<div class="tab-content">
<SplitForm
v-for="(transaction, index) in this.transactions"
v-bind:key="index"
:count="transactions.length"
:transaction="transaction"
:allowed-opposing-types="allowedOpposingTypes"
:custom-fields="customFields"
:date="date"
:time="time"
:index="index"
:transaction-type="transactionType"
:destination-allowed-types="destinationAllowedTypes"
:source-allowed-types="sourceAllowedTypes"
:allow-switch="false"
:submitted-transaction="submittedTransaction"
v-on:uploaded-attachments="uploadedAttachment($event)"
v-on:set-marker-location="storeLocation($event)"
v-on:set-account="storeAccountValue($event)"
v-on:set-date="storeDate($event)"
v-on:set-time="storeTime($event)"
v-on:set-field="storeField($event)"
v-on:remove-transaction="removeTransaction($event)"
v-on:selected-attachments="selectedAttachments($event)"
/>
</div>
<!-- bottom buttons etc -->
<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)"/>
<!-- bottom buttons etc -->
<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>
<div class="col-xl-6 col-lg-6 col-md-12 col-sm-12 col-xs-12">
<!-- buttons -->
<div class="card">
<div class="card-body">
<div class="row">
<div class="col">
<div class="text-xs d-none d-lg-block d-xl-block">
<div class="col-xl-6 col-lg-6 col-md-12 col-sm-12 col-xs-12">
<!-- buttons -->
<div class="card">
<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 class="btn btn-outline-primary btn-block" @click="addTransaction"><i class="far fa-clone"></i> {{ $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-info btn-block" @click="submitTransaction">
<span v-if="enableSubmit"><i class="far fa-save"></i> {{ $t('firefly.update_transaction') }}</span>
<span v-if="!enableSubmit"><i class="fas fa-spinner fa-spin"></i></span>
</button>
</div>
</div>
<div class="row">
<div class="col">
&nbsp;
</div>
<button class="btn btn-outline-primary btn-block" @click="addTransaction"><i class="far fa-clone"></i> {{ $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-info btn-block" @click="submitTransaction">
<span v-if="enableSubmit"><i class="far fa-save"></i> {{ $t('firefly.update_transaction') }}</span>
<span v-if="!enableSubmit"><i class="fas fa-spinner fa-spin"></i></span>
</button>
</div>
</div>
<div class="row">
<div class="col">
&nbsp;
</div>
<div class="col">
<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 class="col">
<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>
</form>
</div>
</template>
@@ -114,7 +116,7 @@ import Alert from '../partials/Alert';
import SplitPills from "./SplitPills";
import SplitForm from "./SplitForm";
import TransactionGroupTitle from "./TransactionGroupTitle";
import {getDefaultErrors, getDefaultTransaction, toW3CString} from '../shared/transactions';
import {getDefaultErrors, getDefaultTransaction, toW3CString} from '../../shared/transactions';
export default {
name: "Edit",
@@ -463,7 +465,7 @@ export default {
let newTransactionCount = this.transactions.length;
console.log('Found ' + this.transactions.length + ' split(s).');
if(newTransactionCount > 1 && typeof submission.group_title === 'undefined' && (null === this.originalGroupTitle || '' === this.originalGroupTitle)) {
if (newTransactionCount > 1 && typeof submission.group_title === 'undefined' && (null === this.originalGroupTitle || '' === this.originalGroupTitle)) {
submission.group_title = this.transactions[0].description;
}

View File

@@ -51,7 +51,6 @@
<TransactionAccount
v-model="sourceAccount"
v-on="$listeners"
:allowed-opposing-types="allowedOpposingTypes"
:destination-allowed-types="destinationAllowedTypes"
:errors="transaction.errors.source"
:index="index"
@@ -76,7 +75,6 @@
<TransactionAccount
v-model="destinationAccount"
v-on="$listeners"
:allowed-opposing-types="allowedOpposingTypes"
:destination-allowed-types="destinationAllowedTypes"
:errors="transaction.errors.destination"
:index="index"
@@ -369,11 +367,6 @@ export default {
required: false,
default: []
},
allowedOpposingTypes: {
type: Object,
required: false,
default: {}
},
// allow switch?
allowSwitch: {
type: Boolean,
@@ -382,11 +375,6 @@ export default {
}
},
// watch: {
// allowedOpposingTypes: function() {
// console.log('SplitForm noticed change in allowedOpposingTypes');
// }
// },
methods: {
removeTransaction: function () {
// console.log('Will remove transaction ' + this.index);

View File

@@ -26,9 +26,11 @@
</span>
<span v-if="'any' === this.transactionType" class="text-muted">&nbsp;</span>
</div>
<!--
<div class="btn-group d-flex">
<button class="btn btn-light" @click="switchAccounts">&harr;</button>
</div>
-->
</div>
</template>
@@ -37,9 +39,9 @@ export default {
name: "SwitchAccount",
props: ['index', 'transactionType'],
methods: {
switchAccounts() {
this.$emit('switch-accounts', this.index);
}
// switchAccounts() {
// this.$emit('switch-accounts', this.index);
// }
}
}
</script>

View File

@@ -92,10 +92,6 @@ export default {
type: Array,
default: () => ([])
},
allowedOpposingTypes: {
type: Object,
default: () => ({})
},
transactionType: {
type: String,
default: 'any'
@@ -161,9 +157,6 @@ export default {
}
},
watch: {
// allowedOpposingTypes: function () {
// console.log(this.direction + ' account noticed change in allowedOpposingTypes');
// },
sourceAllowedTypes: function (value) {
// console.log(this.direction + ' account noticed change in sourceAllowedTypes');
// console.log(value);
@@ -193,7 +186,15 @@ export default {
this.accountName = this.account.name_with_balance;
},
accountName: function (value) {
if('source' === this.direction && 'Deposit' !== this.transactionType) {
return;
}
if('destination' === this.direction && 'Withdrawal' !== this.transactionType) {
return;
}
if (false === this.selectedAccountTrigger) {
console.log('User submitted manual thing.');
// console.log('Save to change name!');
this.$emit('set-account',
{
@@ -212,22 +213,6 @@ export default {
}
this.selectedAccountTrigger = false;
},
account: function (value) {
let opposingAccounts = [];
let type = value.type ? value.type : 'no_type';
if ('undefined' !== typeof this.allowedOpposingTypes[this.direction]) {
if ('undefined' !== typeof this.allowedOpposingTypes[this.direction][type]) {
opposingAccounts = this.allowedOpposingTypes[this.direction][type];
}
}
if ('source' === this.direction) {
this.$emit('set-dest-types', opposingAccounts);
}
if ('destination' === this.direction) {
this.$emit('set-src-types', opposingAccounts);
}
},
value: function (value) {
// console.log('Index ' + this.index + ' nwAct: ', value);
// console.log(this.direction + ' account overruled by external forces.');

View File

@@ -31,7 +31,6 @@
:title="$t('firefly.bill')"
autocomplete="off"
name="bill_id[]"
v-on:submit.prevent
>
<option v-for="bill in this.billList" :label="bill.name" :value="bill.id">{{ bill.name }}</option>

View File

@@ -31,7 +31,6 @@
:title="$t('firefly.budget')"
autocomplete="off"
name="budget_id[]"
v-on:submit.prevent
>
<option v-for="budget in this.budgetList" :label="budget.name" :value="budget.id">{{ budget.name }}</option>
</select>

View File

@@ -35,7 +35,6 @@
class="form-control"
type="date"
@change="setFieldValue($event, name)"
v-on:submit.prevent
>
</div>
</div>

View File

@@ -35,6 +35,7 @@
name="date[]"
type="date"
>
<!--
<input
ref="time"
v-model="timeStr"
@@ -46,6 +47,7 @@
name="time[]"
type="time"
>
-->
</div>
<span v-if="errors.length > 0">
<span v-for="error in errors" class="text-danger small">{{ error }}<br/></span>

View File

@@ -31,7 +31,6 @@
:title="$t('firefly.piggy_bank')"
autocomplete="off"
name="piggy_bank_id[]"
v-on:submit.prevent
>
<option v-for="piggy in this.piggyList" :label="piggy.name_with_balance" :value="piggy.id">{{ piggy.name_with_balance }}</option>

36
frontend/src/pages/accounts/create.js vendored Normal file
View File

@@ -0,0 +1,36 @@
/*
* create.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/>.
*/
require('../../bootstrap');
import Create from "../../components/accounts/Create";
// i18n
let i18n = require('../../i18n');
let props = {};
new Vue({
i18n,
render(createElement) {
return createElement(Create, {props: props});
}
}).$mount('#accounts_create');

View File

@@ -25,12 +25,14 @@ import store from "../../components/store";
import {BPagination, BTable} from 'bootstrap-vue';
import Calendar from "../../components/dashboard/Calendar";
import IndexOptions from "../../components/accounts/IndexOptions";
//import Vuex from "vuex";
// i18n
let i18n = require('../../i18n');
let props = {};
// TODO: long lists are slow to load. Fix this.
// TODO add interest for liabilities
Vue.component('b-table', BTable);
Vue.component('b-pagination', BPagination);
//Vue.use(Vuex);

View File

@@ -43,6 +43,8 @@ import store from '../components/store';
* vue, uiv and vuei18n are in app_vue.js
*/
// TODO pretty sure not all categories, budgets and other objects are picked up because they're paginated.
require('../bootstrap');
require('chart.js');

View File

@@ -28,6 +28,12 @@ Vue.config.productionTip = false;
// i18n
let i18n = require('../../i18n');
// TODO take transaction type from URL. Simplifies a lot of code.
// TODO make sure the enter button works.
// TODO add preferences in sidebar
// TODO If I change the date box at all even if you just type over it with the current date, it posts back a day.
// TODO Cash accounts do not work
let props = {};
new Vue({
i18n,