5.5.0-beta.2

This commit is contained in:
James Cole
2021-03-14 20:05:06 +01:00
1093 changed files with 39447 additions and 19567 deletions

View File

@@ -1,5 +1,25 @@
#!/usr/bin/env bash
#
# build.sh
# 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/>.
#
[ -d "~/Sites" ] && exit 1;
# build translations.
@@ -8,8 +28,11 @@ php /sites/FF3/dev/tools/cli.php ff3:json-translations v2
# remove old stuff
rm -rf public/
rm -rf ../public/fonts
rm -rf ../public/images
rm -rf ../public/v2/js
rm -rf ../public/v2/css
mkdir -p public/js
mkdir -p public/css
# build new stuff
yarn install
@@ -27,4 +50,4 @@ yarn prod
cp -r fonts ../public
# remove built stuff
rm -rf public
rm -rf public

View File

@@ -18,5 +18,7 @@
"/public/js/register.js": "/public/js/register.js",
"/public/js/register.js.map": "/public/js/register.js.map",
"/public/js/transactions/create.js": "/public/js/transactions/create.js",
"/public/js/transactions/create.js.map": "/public/js/transactions/create.js.map"
"/public/js/transactions/create.js.map": "/public/js/transactions/create.js.map",
"/public/js/transactions/edit.js": "/public/js/transactions/edit.js",
"/public/js/transactions/edit.js.map": "/public/js/transactions/edit.js.map"
}

View File

@@ -10,16 +10,17 @@
"production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
},
"devDependencies": {
"admin-lte": "^3.0",
"axios": "^0.21",
"cross-env": "^7.0",
"laravel-mix": "^5.0.9",
"laravel-mix-bundle-analyzer": "^1.0.5",
"lodash": "^4.17.20",
"lodash": "^4.17.21",
"lodash.clonedeep": "^4.5.0",
"node-forge": ">=0.10.0",
"resolve-url-loader": "^3.1.2",
"sass": "^1.32.2",
"sass-loader": "^10.1.0",
"sass": "^1.32.8",
"sass-loader": "^10.1.1",
"vue": "^2.6.12",
"vue-i18n": "^8.22.2",
"vue-template-compiler": "^2.6.12"
@@ -27,20 +28,22 @@
"dependencies": {
"@fortawesome/fontawesome-free": "^5.15.1",
"@johmun/vue-tags-input": "^2.1.0",
"@popperjs/core": "^2.6.0",
"@popperjs/core": "^2.8.6",
"bootstrap": "^4.5.3",
"chart.js": "^2.9.4",
"icheck-bootstrap": "^3.0.1",
"jquery": "^3.5.1",
"jquery-ui": "^1.12.1",
"leaflet": "^1.7.1",
"overlayscrollbars": "^1.13.1",
"popper.js": "^1.16.1",
"tempusdominus": "^5.16.0",
"v-calendar": "^2.2.1",
"v-calendar": "^2.2.4",
"vue-chartjs": "^3.5.1",
"vue-router": "^3.4.9",
"vue-simple-suggest": "^1.10.3",
"vue-typeahead-bootstrap": "^2.5.5",
"vue2-leaflet": "^2.6.0",
"vuex": "^3.6.0"
}
}

View File

@@ -1,5 +1,25 @@
#!/usr/bin/env bash
#
# render.sh
# 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/>.
#
[ -d "~/Sites" ] && exit 1;
# build translations.

59
frontend/src/app.scss vendored
View File

@@ -25,10 +25,59 @@
// iCheck
@import '~icheck-bootstrap/icheck-bootstrap.css';
// AdminLTE
@import 'dist/css/adminlte.css';
//@import 'dist/css/adminlte.css';
//@import 'adminlte/scss/adminlte.css'
//@import '~admin-lte/build/scss/AdminLTE';
// ADMIN LTE
@import '~bootstrap/scss/functions';
@import '~admin-lte/build/scss/bootstrap-variables';
@import '~bootstrap/scss/bootstrap';
// Variables and Mixins
// ---------------------------------------------------
@import '~admin-lte/build/scss/variables';
// Firefly III colors (?)
@import 'scss/variables';
@import '~admin-lte/build/scss/mixins';
@import '~admin-lte/build/scss/parts/core';
// admin LTE components
@import '~admin-lte/build/scss/forms';
@import '~admin-lte/build/scss/progress-bars';
@import '~admin-lte/build/scss/cards';
@import '~admin-lte/build/scss/modals';
//@import '../toasts';
@import '~admin-lte/build/scss/buttons';
//@import '../callout';
@import '~admin-lte/build/scss/alerts';
@import '~admin-lte/build/scss/table';
//@import '../carousel';
// admin LTE extra components
//@import '../small-box';
@import '~admin-lte/build/scss/info-box';
//@import '../timeline';
//@import '../products';
//@import '../direct-chat';
//@import '../users-list';
//@import '../social-widgets';
// admin LTE pages (unused)
// @import 'parts/pages';
// admin LTE plugins (unused)
// @import 'parts/plugins';
// admin LTE misc
@import '~admin-lte/build/scss/miscellaneous';
@import '~admin-lte/build/scss/print';
@import '~admin-lte/build/scss/text';
@import '~admin-lte/build/scss/elevation';
@import '~admin-lte/build/scss/colors';
// Bootstrap
// Already imported by AdminLTE
//@import '~bootstrap/scss/bootstrap';

View File

@@ -1,3 +1,23 @@
/*
* adminlte.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/>.
*/
/*!
* AdminLTE v3.0.5 (https://adminlte.io)
* Copyright 2014-2020 Colorlib <http://colorlib.com>

View File

@@ -23,9 +23,9 @@
</template>
<script>
export default {
name: "Edit"
}
export default {
name: "Edit"
}
</script>
<style scoped>

View File

@@ -26,10 +26,10 @@
<h3 class="card-title">Title thing</h3>
<div class="card-tools">
<div class="input-group input-group-sm" style="width: 150px;">
<input type="text" name="table_search" class="form-control float-right" placeholder="Search">
<input class="form-control float-right" name="table_search" placeholder="Search" type="text">
<div class="input-group-append">
<button type="submit" class="btn btn-default">
<button class="btn btn-default" type="submit">
<i class="fas fa-search"></i>
</button>
</div>
@@ -43,7 +43,7 @@
<tr>
<th scope="col">&nbsp;</th>
<th scope="col">{{ $t('list.name') }}</th>
<th scope="col" v-if="'asset' === $props.accountTypes">{{ $t('list.role') }}</th>
<th v-if="'asset' === $props.accountTypes" scope="col">{{ $t('list.role') }}</th>
<th scope="col">{{ $t('list.iban') }}</th>
<th scope="col" style="text-align: right;">{{ $t('list.currentBalance') }}</th>
<th scope="col">{{ $t('list.balanceDiff') }}</th>

View File

@@ -19,15 +19,15 @@
-->
<template>
<div>
I am a show
</div>
<div>
I am a show
</div>
</template>
<script>
export default {
name: "Show"
}
export default {
name: "Show"
}
</script>
<style scoped>

View File

@@ -24,91 +24,91 @@
<script>
import FormatLabel from "../charts/FormatLabel";
import FormatLabel from "../charts/FormatLabel";
export default {
name: "DefaultBarOptions",
data() {
return {}
export default {
name: "DefaultBarOptions",
data() {
return {}
},
methods: {
getDefaultOptions() {
return {
type: 'bar',
layout: {
padding: {
left: 50,
right: 50,
top: 0,
bottom: 0
},
},
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;
}
}
}
};
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>
<style scoped>

View File

@@ -105,7 +105,7 @@ export default {
callback: function (value, index, values) {
// date format
let dateObj = new Date(value);
let options = { year: 'numeric', month: 'long', day: 'numeric' };
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);

View File

@@ -23,9 +23,9 @@
</template>
<script>
export default {
name: "DefaultPieOptions"
}
export default {
name: "DefaultPieOptions"
}
</script>
<style scoped>

View File

@@ -18,56 +18,56 @@
- 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 = "";
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;
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;
},
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

@@ -26,8 +26,8 @@
<td style="vertical-align: middle">
<div class="progress progress active">
<div class="progress-bar bg-success progress-bar-striped" role="progressbar"
:aria-valuenow="budgetLimit.pctGreen" aria-valuemin="0" aria-valuemax="100" :style="'width: '+ budgetLimit.pctGreen + '%;'">
<div :aria-valuenow="budgetLimit.pctGreen" :style="'width: '+ budgetLimit.pctGreen + '%;'"
aria-valuemax="100" aria-valuemin="0" class="progress-bar bg-success progress-bar-striped" role="progressbar">
<span v-if="budgetLimit.pctGreen > 35">
Spent
{{ Intl.NumberFormat(locale, {style: 'currency', currency: budgetLimit.currency_code}).format(budgetLimit.spent) }}
@@ -38,8 +38,8 @@
</div>
<div class="progress-bar bg-warning progress-bar-striped" role="progressbar"
:aria-valuenow="budgetLimit.pctOrange" aria-valuemin="0" aria-valuemax="100" :style="'width: '+ budgetLimit.pctOrange + '%;'">
<div :aria-valuenow="budgetLimit.pctOrange" :style="'width: '+ budgetLimit.pctOrange + '%;'"
aria-valuemax="100" aria-valuemin="0" class="progress-bar bg-warning progress-bar-striped" role="progressbar">
<span v-if="budgetLimit.pctRed <= 50 && budgetLimit.pctOrange > 35">
Spent
{{ Intl.NumberFormat(locale, {style: 'currency', currency: budgetLimit.currency_code}).format(budgetLimit.spent) }}
@@ -48,15 +48,25 @@
</span>
</div>
<div class="progress-bar bg-danger progress-bar-striped" role="progressbar"
:aria-valuenow="budgetLimit.pctRed" aria-valuemin="0" aria-valuemax="100" :style="'width: '+ budgetLimit.pctRed + '%;'">
<span v-if="budgetLimit.pctOrange <= 50 && budgetLimit.pctRed > 35">
<div :aria-valuenow="budgetLimit.pctRed" :style="'width: '+ budgetLimit.pctRed + '%;'"
aria-valuemax="100" aria-valuemin="0" class="progress-bar bg-danger progress-bar-striped" role="progressbar">
<span v-if="budgetLimit.pctOrange <= 50 && budgetLimit.pctRed > 35" class="text-muted">
Spent
{{ Intl.NumberFormat(locale, {style: 'currency', currency: budgetLimit.currency_code}).format(budgetLimit.spent) }}
of
{{ 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">
&nbsp;
Spent
{{ Intl.NumberFormat(locale, {style: 'currency', currency: budgetLimit.currency_code}).format(budgetLimit.spent) }}
of
{{ 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) }}
@@ -65,8 +75,8 @@
</small>
</td>
<td style="width:10%;" class="align-middle d-none d-lg-table-cell">
<span class="text-success" v-if="parseFloat(budgetLimit.amount) + parseFloat(budgetLimit.spent) > 0">
<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',
@@ -74,10 +84,10 @@
}).format(parseFloat(budgetLimit.amount) + parseFloat(budgetLimit.spent))
}}
</span>
<span class="text-muted" v-if="0.0 === parseFloat(budgetLimit.amount) + parseFloat(budgetLimit.spent)">
<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 class="text-danger" v-if="parseFloat(budgetLimit.amount) + parseFloat(budgetLimit.spent) < 0">
<span v-if="parseFloat(budgetLimit.amount) + parseFloat(budgetLimit.spent) < 0" class="text-danger">
{{
Intl.NumberFormat(locale, {
style: 'currency',
@@ -93,7 +103,7 @@
<script>
export default {
name: "BudgetLimitRow",
mounted() {
created() {
this.locale = localStorage.locale ?? 'en-US';
},
data() {

View File

@@ -26,13 +26,13 @@
<div class="card-body table-responsive p-0">
<table class="table table-sm">
<tbody>
<BudgetLimitRow v-bind:key="key" v-for="(budgetLimit, key) in budgetLimits" :budgetLimit="budgetLimit" />
<BudgetRow v-bind:key="key" v-for="(budget, key) in budgets" :budget="budget" />
<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 href="./budgets" class="btn btn-default button-sm"><i class="far fa-money-bill-alt"></i> {{ $t('firefly.go_to_budgets') }}</a>
<a class="btn btn-default button-sm" href="./budgets"><i class="far fa-money-bill-alt"></i> {{ $t('firefly.go_to_budgets') }}</a>
</div>
</div>
</template>
@@ -40,6 +40,7 @@
<script>
import BudgetLimitRow from "./BudgetLimitRow";
import BudgetRow from "./BudgetRow";
export default {
name: "BudgetListGroup",
components: {BudgetLimitRow, BudgetRow},

View File

@@ -18,6 +18,7 @@
- along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<template>
<tr>
<td style="width:25%;">
@@ -34,7 +35,7 @@
<script>
export default {
name: "BudgetRow",
mounted() {
created() {
this.locale = localStorage.locale ?? 'en-US';
},
data() {

View File

@@ -30,43 +30,41 @@
</div>
<date-picker
v-model="range"
mode="date"
rows="2"
: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
class="btn btn-secondary btn-sm"
@click="togglePopover({ placement: 'auto-start', positionFixed:true })"
:title="$t('firefly.custom_period')" class="btn btn-secondary btn-sm"
@click="togglePopover({ placement: 'auto-start', positionFixed: true })"
><i class="fas fa-calendar-alt"></i></button>
<button
class="btn btn-secondary"
<button :title="$t('firefly.reset_to_current')"
class="btn btn-secondary"
@click="resetDate"
><i class="fas fa-history"></i></button>
<button class="btn btn-secondary dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true"
aria-expanded="false">
<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">
<i class="fas fa-list"></i>
</button>
<div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
<a class="dropdown-item" href="#">(prev period)</a>
<a class="dropdown-item" href="#">(next period)</a>
<a class="dropdown-item" href="#">(this week?)</a>
<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 type="hidden"
<input v-on="inputEvents.start"
:class="isDragging ? 'text-gray-600' : 'text-gray-900'"
:value="inputValue.start"
v-on="inputEvents.start"
type="hidden"
/>
<input type="hidden"
<input v-on="inputEvents.end"
:class="isDragging ? 'text-gray-600' : 'text-gray-900'"
:value="inputValue.end"
v-on="inputEvents.end"
type="hidden"
/>
</div>
</div>
@@ -76,28 +74,109 @@
</template>
<script>
import {createNamespacedHelpers} from "vuex";
const {mapState, mapGetters, mapActions, mapMutations} = createNamespacedHelpers('dashboard/index')
export default {
name: "Calendar",
created() {
// this.locale = localStorage.locale ?? 'en-US';
// this.$store.commit('increment');
// console.log(this.$store.state.count);
// get dates for current period (history button):
// get dates for optional periods (dropdown) + descriptions.
this.ready = true;
this.locale = localStorage.locale ?? 'en-US';
},
data() {
return {
locale: 'en-US',
ready: false,
range: {
start: new Date(window.sessionStart),
end: new Date(window.sessionEnd),
start: null,
end: null,
},
defaultRange: {
start: new Date(window.sessionStart),
end: new Date(window.sessionEnd),
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;
return false;
}
},
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.periods = [];
// create periods.
// 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
},
range: function (value) {
//console.log('User updated range');
this.setStart(value.start);
this.setEnd(value.end);
}
}
}
</script>

View File

@@ -62,8 +62,5 @@
<script>
export default {
name: "Dashboard",
created() {},
computed: {},
methods: {}
}
</script>

View File

@@ -24,35 +24,93 @@
<h3 class="card-title">{{ $t('firefly.yourAccounts') }}</h3>
</div>
<div class="card-body">
<div>
<canvas id="mainAccountsChart" style="min-height: 400px; height: 400px; max-height: 400px; max-width: 100%;"></canvas>
<div v-if="!loading">
<MainAccountChart v-if="!loading && !error" :chart-data="dataCollection" :options="chartOptions"/>
</div>
<div v-if="loading && !error" class="text-center">
<i class="fas fa-spinner fa-spin"></i>
</div>
<div v-if="error" class="text-center">
<i class="fas fa-exclamation-triangle text-danger"></i>
</div>
</div>
<div class="card-footer">
<a href="./accounts/asset" class="btn btn-default button-sm"><i class="far fa-money-bill-alt"></i> {{ $t('firefly.go_to_asset_accounts') }}</a>
<a class="btn btn-default button-sm" href="./accounts/asset"><i class="far fa-money-bill-alt"></i> {{ $t('firefly.go_to_asset_accounts') }}</a>
</div>
</div>
</template>
<script>
import DataConverter from "../charts/DataConverter";
import DefaultLineOptions from "../charts/DefaultLineOptions";
import {createNamespacedHelpers} from "vuex";
import MainAccountChart from "./MainAccountChart";
const {mapState, mapGetters, mapActions, mapMutations} = createNamespacedHelpers('dashboard/index')
export default {
name: "MainAccount",
components: {MainAccountChart},
data() {
return {
loading: true,
error: false,
ready: false,
dataCollection: {},
chartOptions: {}
}
},
created() {
axios.get('./api/v1/chart/account/overview?start=' + window.sessionStart + '&end=' + window.sessionEnd)
.then(response => {
let chartData = DataConverter.methods.convertChart(response.data);
chartData = DataConverter.methods.colorizeLineData(chartData);
let lineChartCanvas = $('#mainAccountsChart').get(0).getContext('2d');
new Chart(lineChartCanvas, {
type: 'line',
data: chartData,
options: DefaultLineOptions.methods.getDefaultOptions()
this.ready = true;
this.chartOptions = DefaultLineOptions.methods.getDefaultOptions();
},
computed: {
...mapGetters([
'start',
'end'
]),
'datesReady': function () {
return null !== this.start && null !== this.end && this.ready;
}
},
watch: {
datesReady: function (value) {
if (true === value) {
// console.log(this.chartOptions);
this.initialiseChart();
}
},
start: function () {
this.initialiseChart();
},
end: function () {
this.initialiseChart();
},
},
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 url = './api/v1/chart/account/overview?start=' + startStr + '&end=' + endStr;
// console.log('URL is ' + url);
axios.get(url)
.then(response => {
let chartData = DataConverter.methods.convertChart(response.data);
chartData = DataConverter.methods.colorizeLineData(chartData);
this.dataCollection = chartData;
this.loading = false;
})
.catch(error => {
// console.log('Has error!');
// console.log(error);
this.error = true;
// console.error(error);
});
});
}
},
}
</script>

View File

@@ -1,4 +1,3 @@
<!--
- MainAccountChart.vue
- Copyright (c) 2020 james@firefly-iii.org
@@ -20,21 +19,17 @@
-->
<script>
import {Line} from 'vue-chartjs'
export default {
extends: Line,
props: ['options', 'chartData'],
import {Line, mixins} from 'vue-chartjs'
mounted() {
// this.chartData is created in the mixin.
// If you want to pass options please create a local options object
this.renderChart(this.chartData, this.options)
}
}
const {reactiveProp} = mixins
export default {
extends: Line,
mixins: [reactiveProp],
props: ['options'],
mounted() {
this.renderChart(this.chartData, this.options)
}
}
</script>
<style scoped>
</style>

View File

@@ -19,17 +19,54 @@
-->
<template>
<div class="row">
<div v-bind:class="{ 'col-lg-12': 1 === accounts.length, 'col-lg-6': 2 === accounts.length, 'col-lg-4': accounts.length > 2 }"
v-for="account in accounts">
<div class="card">
<div class="card-header">
<h3 class="card-title"><a :href="account.uri">{{ account.title }}</a></h3>
<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">
<i class="fas fa-spinner fa-spin"></i>
</div>
</div>
</div>
<div class="card-body table-responsive p-0">
<transaction-list-large :transactions="account.transactions" v-if="1===accounts.length" :account_id="account.id"/>
<transaction-list-medium :transactions="account.transactions" v-if="2===accounts.length" :account_id="account.id"/>
<transaction-list-small :transactions="account.transactions" v-if="accounts.length > 2" :account_id="account.id"/>
</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">
<i class="fas fa-exclamation-triangle text-danger"></i>
</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>
<transaction-list-large v-if="1===accounts.length" :account_id="account.id" :transactions="account.transactions"/>
<transaction-list-medium v-if="2===accounts.length" :account_id="account.id" :transactions="account.transactions"/>
<transaction-list-small v-if="accounts.length > 2" :account_id="account.id" :transactions="account.transactions"/>
</div>
</div>
</div>
</div>
</div>
@@ -37,53 +74,101 @@
</template>
<script>
import {createNamespacedHelpers} from "vuex";
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() {
axios.get('./api/v1/preferences/frontpageAccounts')
.then(response => {
this.loadAccounts(response);
}
);
this.locale = localStorage.locale ?? 'en-US';
this.ready = true;
},
methods:
{
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: '',
uri: '',
transactions: []
});
this.loadSingleAccount(key, accountIds[key]);
}
}
},
loadSingleAccount(key, accountId) {
axios.get('./api/v1/accounts/' + accountId)
.then(response => {
this.accounts[key].title = response.data.data.attributes.name;
this.accounts[key].uri = './accounts/show/' + response.data.data.id;
this.loadTransactions(key, accountId);
}
);
},
loadTransactions(key, accountId) {
axios.get('./api/v1/accounts/' + accountId + '/transactions?page=1&limit=10')
.then(response => {
this.accounts[key].transactions = response.data.data;
}
);
},
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: '',
current_balance: '',
currency_code: 'EUR',
transactions: []
});
this.loadSingleAccount(key, accountIds[key]);
}
}
},
loadSingleAccount(key, accountId) {
axios.get('./api/v1/accounts/' + accountId)
.then(response => {
this.accounts[key].title = response.data.data.attributes.name;
this.accounts[key].url = './accounts/show/' + response.data.data.id;
this.accounts[key].current_balance = response.data.data.attributes.current_balance;
this.accounts[key].currency_code = response.data.data.attributes.currency_code;
this.loadTransactions(key, accountId);
}
);
},
loadTransactions(key, accountId) {
let startStr = this.start.toISOString().split('T')[0];
let endStr = this.end.toISOString().split('T')[0];
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

@@ -23,7 +23,20 @@
<div class="card-header">
<h3 class="card-title">{{ $t('firefly.bills') }}</h3>
</div>
<div class="card-body table-responsive p-0">
<!-- body if loading -->
<div v-if="loading && !error" class="card-body">
<div class="text-center">
<i class="fas fa-spinner fa-spin"></i>
</div>
</div>
<!-- body if error -->
<div v-if="error" class="card-body">
<div class="text-center">
<i class="fas fa-exclamation-triangle text-danger"></i>
</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>
@@ -34,47 +47,101 @@
</thead>
<tbody>
<tr v-for="bill in this.bills">
<td><a :href="'./bills/show' + bill.id" :title="bill.attributes.name">{{ bill.attributes.name }}</a>
~{{
Intl.NumberFormat(locale, {style: 'currency', currency: bill.attributes.currency_code}).format((parseFloat(bill.attributes.amount_min) +
parseFloat(bill.attributes.amount_max)) / 2)
}}
<br />
<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="payDate in bill.attributes.pay_dates">
{{ new Intl.DateTimeFormat(locale, {year: 'numeric', month: 'long', day: 'numeric'}).format(new Date(payDate)) }}
<br />
</span>
<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 href="./bills" class="btn btn-default button-sm"><i class="far fa-money-bill-alt"></i> {{ $t('firefly.go_to_bills') }}</a>
<a class="btn btn-default button-sm" href="./bills"><i class="far fa-money-bill-alt"></i> {{ $t('firefly.go_to_bills') }}</a>
</div>
</div>
</template>
<script>
import {createNamespacedHelpers} from "vuex";
const {mapState, mapGetters, mapActions, mapMutations} = createNamespacedHelpers('dashboard/index')
export default {
name: "MainBillsList",
computed: {
locale() {
return this.$store.getters.locale;
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() {
axios.get('./api/v1/bills?start=' + window.sessionStart + '&end=' + window.sessionEnd)
.then(response => {
this.loadBills(response.data.data);
}
);
this.ready = true;
this.locale = localStorage.locale ?? 'en-US';
},
components: {},
methods: {
loadBills(data) {
initialiseBills: function () {
this.loading = true;
this.bills = [];
let startStr = this.start.toISOString().split('T')[0];
let endStr = this.end.toISOString().split('T')[0];
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) {
@@ -85,12 +152,9 @@ export default {
}
}
}
this.error = false;
this.loading = false;
}
},
data() {
return {
bills: []
}
},
}
}
</script>

View File

@@ -1,59 +0,0 @@
<!--
- MainBudgetChart.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.budgets') }}</h3>
</div>
<div class="card-body">
<div style="position: relative;">
<canvas id="mainBudgetChart" style="min-height: 400px; height: 400px; max-height: 400px; max-width: 100%;"></canvas>
</div>
</div>
<div class="card-footer">
<a href="./budgets" class="btn btn-default button-sm"><i class="far fa-money-bill-alt"></i> {{ $t('firefly.go_to_budgets') }}</a>
</div>
</div>
</template>
<script>
import DefaultBarOptions from "../charts/DefaultBarOptions";
import DataConverter from "../charts/DataConverter";
export default {
name: "MainBudget",
created() {
axios.get('./api/v1/chart/budget/overview?start=' + window.sessionStart + '&end=' + window.sessionEnd)
.then(response => {
let chartData = DataConverter.methods.convertChart(response.data);
let stackedBarChartCanvas = $('#mainBudgetChart').get(0).getContext('2d')
new Chart(stackedBarChartCanvas, {
type: 'bar',
data: chartData,
options: DefaultBarOptions.methods.getDefaultOptions()
});
});
},
}
</script>
<style scoped>
</style>

View File

@@ -21,42 +21,55 @@
<template>
<div>
<!-- daily budgets (will be the exception, I expect) -->
<div class="row">
<div class="col-xl-6 col-lg-12 col-md-12 col-sm-12 col-xs-12" v-if="budgetLimits.daily.length > 0">
<BudgetListGroup :title="$t('firefly.daily_budgets')" :budgetLimits=budgetLimits.daily
<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 class="col-xl-6 col-lg-12 col-md-12 col-sm-12 col-xs-12" v-if="budgetLimits.weekly.length > 0">
<BudgetListGroup :title="$t('firefly.weekly_budgets')" :budgetLimits=budgetLimits.weekly
<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 class="col-xl-6 col-lg-12 col-md-12 col-sm-12 col-xs-12" v-if="budgetLimits.monthly.length > 0">
<BudgetListGroup :title="$t('firefly.monthly_budgets')" :budgetLimits=budgetLimits.monthly
<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 class="col-xl-6 col-lg-12 col-md-12 col-sm-12 col-xs-12" v-if="budgetLimits.quarterly.length > 0">
<BudgetListGroup :title="$t('firefly.quarterly_budgets')" :budgetLimits=budgetLimits.quarterly
<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 class="col-xl-6 col-lg-12 col-md-12 col-sm-12 col-xs-12" v-if="budgetLimits.half_year.length > 0">
<BudgetListGroup :title="$t('firefly.half_year_budgets')" :budgetLimits=budgetLimits.half_year
<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 class="col-xl-6 col-lg-12 col-md-12 col-sm-12 col-xs-12" v-if="budgetLimits.yearly.length > 0">
<BudgetListGroup :title="$t('firefly.yearly_budgets')" :budgetLimits=budgetLimits.yearly
<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 class="col-xl-6 col-lg-12 col-md-12 col-sm-12 col-xs-12" v-if="budgetLimits.other.length > 0 || rawBudgets.length > 0">
<BudgetListGroup :title="$t('firefly.other_budgets')" :budgetLimits=budgetLimits.other :budgets="rawBudgets"
<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">
<i class="fas fa-spinner fa-spin"></i>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import BudgetListGroup from "./BudgetListGroup";
import {createNamespacedHelpers} from "vuex";
const {mapState, mapGetters, mapActions, mapMutations} = createNamespacedHelpers('dashboard/index')
export default {
name: "MainBudgetList",
@@ -76,19 +89,59 @@ export default {
budgets: {},
rawBudgets: [],
locale: 'en-US',
ready: false,
loading: true,
error: false
}
},
created() {
this.ready = true;
this.locale = localStorage.locale ?? 'en-US';
this.collectData();
},
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:
{
collectData() {
this.getBudgets();
},
getBudgets() {
axios.get('./api/v1/budgets?start=' + window.sessionStart + '&end=' + window.sessionEnd)
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];
axios.get('./api/v1/budgets?start=' + startStr + '&end=' + endStr)
.then(response => {
this.parseBudgets(response.data);
}
@@ -117,12 +170,13 @@ export default {
}
this.getBudgetLimits();
},
getBudgetLimits() {
axios.get('./api/v1/budgets/limits?start=' + window.sessionStart + '&end=' + window.sessionEnd)
let startStr = this.start.toISOString().split('T')[0];
let endStr = this.end.toISOString().split('T')[0];
axios.get('./api/v1/budget-limits?start=' + startStr + '&end=' + endStr)
.then(response => {
this.parseBudgetLimits(response.data);
this.loading = false;
}
);
},
@@ -174,53 +228,8 @@ export default {
let period = data.data[key].attributes.period ?? 'other';
this.budgetLimits[period].push(obj);
}
}
// // loop budgets (and do what?)
// for (let key in data.included) {
// if (data.included.hasOwnProperty(key) && /^0$|^[1-9]\d*$/.test(key) && key <= 4294967294) {
// let obj = {
// name: data.included[key].attributes.name,
// id: data.included[key].id,
// };
// this.budgets[data.included[key].id] = obj;
// }
// }
// loop budget limits:
// for (let key in data.data) {
// if (data.data.hasOwnProperty(key) && /^0$|^[1-9]\d*$/.test(key) && key <= 4294967294) {
// let pctGreen = 0;
// let pctOrange = 0;
// let pctRed = 0;
//
//
// let obj = {
// id: data.data[key].id,
// amount: data.data[key].attributes.amount,
// budget_id: data.data[key].attributes.budget_id,
// currency_id: data.data[key].attributes.currency_id,
// currency_code: data.data[key].attributes.currency_code,
// period: data.data[key].attributes.period,
// start: new Date(data.data[key].attributes.start),
// end: new Date(data.data[key].attributes.end),
// spent: data.data[key].attributes.spent,
// pctGreen: pctGreen,
// pctOrange: pctOrange,
// pctRed: pctRed,
// };
//
//
//
// let period = data.data[key].attributes.period ?? 'other';
// this.budgetLimits[period].push(obj);
// }
// }
},
filterBudgets(budgetId, currencyId) {
for (let key in this.rawBudgets) {

View File

@@ -1,60 +0,0 @@
<!--
- MainCategoryChart.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>
<div class="card-body">
<div>
<canvas id="mainCategoryChart" style="min-height: 400px; height: 400px; max-height: 400px; max-width: 100%;"></canvas>
</div>
</div>
<div class="card-footer">
<a href="./categories" class="btn btn-default button-sm"><i class="far fa-money-bill-alt"></i> {{ $t('firefly.go_to_categories') }}</a>
</div>
</div>
</template>
<script>
import DefaultBarOptions from "../charts/DefaultBarOptions";
import DataConverter from "../charts/DataConverter";
export default {
name: "MainCategory",
created() {
axios.get('./api/v1/chart/category/overview?start=' + window.sessionStart + '&end=' + window.sessionEnd)
.then(response => {
let chartData = DataConverter.methods.convertChart(response.data);
chartData = DataConverter.methods.colorizeLineData(chartData);
let stackedBarChartCanvas = $('#mainCategoryChart').get(0).getContext('2d')
new Chart(stackedBarChartCanvas, {
type: 'bar',
data: chartData,
options: DefaultBarOptions.methods.getDefaultOptions()
});
});
},
}
</script>
<style scoped>
</style>

View File

@@ -23,22 +23,32 @@
<div class="card-header">
<h3 class="card-title">{{ $t('firefly.categories') }}</h3>
</div>
<div class="card-body table-responsive p-0">
<!-- body if loading -->
<div v-if="loading && !error" class="card-body">
<div class="text-center">
<i class="fas fa-spinner fa-spin"></i>
</div>
</div>
<!-- body if error -->
<div v-if="error" class="card-body">
<div class="text-center">
<i class="fas fa-exclamation-triangle text-danger"></i>
</div>
</div>
<!-- body if normal -->
<div v-if="!loading && !error" class="card-body table-responsive p-0">
<table class="table table-sm">
<tbody>
<tr v-for="category in sortedList">
<td style="width:20%;">
<a :href="'./categories/show/' + category.id">{{ category.name }}</a>
<!--<p>Spent: {{ category.spentPct }}</p>
<p>earned: {{ category.earnedPct }}</p>
-->
</td>
<td class="align-middle">
<!-- SPENT -->
<div class="progress" v-if="category.spentPct > 0">
<div class="progress-bar progress-bar-striped bg-danger" role="progressbar" :aria-valuenow="category.spentPct"
:style="{ width: category.spentPct + '%'}" aria-valuemin="0"
aria-valuemax="100">
<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>
@@ -49,12 +59,13 @@
</div>
<!-- EARNED -->
<div class="progress justify-content-end" v-if="category.earnedPct > 0" title="hello2">
<div v-if="category.earnedPct > 0" class="progress justify-content-end" title="hello2">
<span v-if="category.earnedPct <= 20">
{{ Intl.NumberFormat(locale, {style: 'currency', currency: category.currency_code}).format(category.earned) }}
&nbsp;</span>
<div class="progress-bar progress-bar-striped bg-success" role="progressbar" :aria-valuenow="category.earnedPct" :style="{ width: category.earnedPct + '%'}" aria-valuemin="0"
aria-valuemax="100" title="hello">
<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>
@@ -70,12 +81,16 @@
</template>
<script>
import {createNamespacedHelpers} from "vuex";
const {mapState, mapGetters, mapActions, mapMutations} = createNamespacedHelpers('dashboard/index')
export default {
name: "MainCategoryList",
created() {
this.locale = localStorage.locale ?? 'en-US';
this.getCategories();
this.ready = true;
},
data() {
return {
@@ -83,17 +98,55 @@ export default {
categories: [],
sortedList: [],
spent: 0,
earned: 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() {
axios.get('./api/v1/categories?start=' + window.sessionStart + '&end=' + window.sessionEnd)
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];
axios.get('./api/v1/categories?start=' + startStr + '&end=' + endStr)
.then(response => {
this.parseCategories(response.data);
this.loading = false;
}
);
).catch(error => {
this.error = true;
});
},
parseCategories(data) {
for (let key in data.data) {

View File

@@ -1,56 +0,0 @@
<!--
- MainCrebitChart.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">
<!-- debit = expense -->
<h3 class="card-title">{{ $t('firefly.income') }}</h3>
</div>
<div class="card-body table-responsive p-0">
<transaction-list-small :transactions="this.transactions" />
</div>
<div class="card-footer">
<a href="./accounts/revenue" class="btn btn-default button-sm"><i class="far fa-money-bill-alt"></i> {{ $t('firefly.go_to_deposits') }}</a>
</div>
</div>
</template>
<script>
export default {
name: "MainCredit",
components: {},
data() {
return {
transactions: []
}
},
created() {
axios.get('./api/v1/transactions?type=deposit&limit=10&start=' + window.sessionStart + '&end=' + window.sessionEnd)
.then(response => {
this.transactions = response.data.data;
}
);
},
methods: {
},
computed: {},
}
</script>

View File

@@ -23,16 +23,29 @@
<div class="card-header">
<h3 class="card-title">{{ $t('firefly.revenue_accounts') }}</h3>
</div>
<div class="card-body table-responsive p-0">
<!-- body if loading -->
<div v-if="loading && !error" class="card-body">
<div class="text-center">
<i class="fas fa-spinner fa-spin"></i>
</div>
</div>
<!-- body if error -->
<div v-if="error" class="card-body">
<div class="text-center">
<i class="fas fa-exclamation-triangle text-danger"></i>
</div>
</div>
<!-- body if normal -->
<div v-if="!loading && !error" class="card-body table-responsive p-0">
<table class="table table-sm">
<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 class="progress" v-if="entry.pct > 0">
<div class="progress-bar progress-bar-striped bg-success" role="progressbar" :aria-valuenow="entry.pct"
:style="{ width: entry.pct + '%'}" aria-valuemin="0"
aria-valuemax="100">
<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 progress-bar-striped bg-success"
role="progressbar">
<span v-if="entry.pct > 20">
{{ Intl.NumberFormat(locale, {style: 'currency', currency: entry.currency_code}).format(entry.difference_float) }}
</span>
@@ -47,34 +60,76 @@
</table>
</div>
<div class="card-footer">
<a href="./transactions/deposit" class="btn btn-default button-sm"><i class="far fa-money-bill-alt"></i> {{ $t('firefly.go_to_deposits') }}</a>
<a class="btn btn-default button-sm" href="./transactions/deposit"><i class="far fa-money-bill-alt"></i> {{ $t('firefly.go_to_deposits') }}</a>
</div>
</div>
</template>
<script>
import {createNamespacedHelpers} from "vuex";
const {mapState, mapGetters, mapActions, mapMutations} = createNamespacedHelpers('dashboard/index')
export default {
name: "MainCreditList",
data() {
return {
locale: 'en-US',
income: [],
max: 0
max: 0,
loading: true,
error: false
}
},
created() {
this.locale = localStorage.locale ?? 'en-US';
this.getExpenses();
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: {
getExpenses() {
axios.get('./api/v1/insight/income/date/basic?start=' + window.sessionStart + '&end=' + window.sessionEnd)
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];
axios.get('./api/v1/insight/income/revenue?start=' + startStr + '&end=' + endStr)
.then(response => {
// do something with response.
this.parseExpenses(response.data);
});
this.parseIncome(response.data);
this.loading = false;
}).catch(error => {
this.error = true
});
},
parseExpenses(data) {
parseIncome(data) {
for (let mainKey in data) {
if (data.hasOwnProperty(mainKey) && /^0$|^[1-9]\d*$/.test(mainKey) && mainKey <= 4294967294) {
// contains currency info and entries.
@@ -83,12 +138,11 @@ export default {
this.max = data[mainKey].difference_float;
current.pct = 100;
}
if(0 !== parseInt(mainKey)) {
if (0 !== parseInt(mainKey)) {
// calc percentage:
current.pct = (data[mainKey].difference_float / this.max) * 100;
}
this.income.push(current);
}
}
}

View File

@@ -1,62 +0,0 @@
<!--
- MainDebit.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">
<!-- debit = expense -->
<h3 class="card-title">{{ $t('firefly.expense_accounts') }}</h3>
</div>
<div class="card-body">
<div>
<canvas id="mainDebitChart" style="min-height: 400px; height: 400px; max-height: 400px; max-width: 100%;"></canvas>
</div>
</div>
<div class="card-footer">
<a href="./transactions/withdrawals" class="btn btn-default button-sm"><i class="far fa-money-bill-alt"></i> {{ $t('firefly.go_to_expenses') }}</a>
</div>
</div>
</template>
<script>
import DefaultBarOptions from "../charts/DefaultBarOptions";
import DataConverter from "../charts/DataConverter";
export default {
name: "MainDebit",
created() {
axios.get('./api/v1/chart/account/expense?start=' + window.sessionStart + '&end=' + window.sessionEnd)
.then(response => {
let chartData = DataConverter.methods.convertChart(response.data);
chartData = DataConverter.methods.colorizeLineData(chartData);
let stackedBarChartCanvas = $('#mainDebitChart').get(0).getContext('2d')
new Chart(stackedBarChartCanvas, {
type: 'bar',
data: chartData,
options: DefaultBarOptions.methods.getDefaultOptions()
});
});
},
}
</script>
<style scoped>
</style>

View File

@@ -23,16 +23,29 @@
<div class="card-header">
<h3 class="card-title">{{ $t('firefly.expense_accounts') }}</h3>
</div>
<div class="card-body table-responsive p-0">
<!-- body if loading -->
<div v-if="loading && !error" class="card-body">
<div class="text-center">
<i class="fas fa-spinner fa-spin"></i>
</div>
</div>
<!-- body if error -->
<div v-if="error" class="card-body">
<div class="text-center">
<i class="fas fa-exclamation-triangle text-danger"></i>
</div>
</div>
<!-- body if normal -->
<div v-if="!loading && !error" class="card-body table-responsive p-0">
<table class="table table-sm">
<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 class="progress" v-if="entry.pct > 0">
<div class="progress-bar progress-bar-striped bg-danger" role="progressbar" :aria-valuenow="entry.pct"
:style="{ width: entry.pct + '%'}" aria-valuemin="0"
aria-valuemax="100">
<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 progress-bar-striped bg-danger"
role="progressbar">
<span v-if="entry.pct > 20">
{{ Intl.NumberFormat(locale, {style: 'currency', currency: entry.currency_code}).format(entry.difference_float) }}
</span>
@@ -47,32 +60,74 @@
</table>
</div>
<div class="card-footer">
<a href="./transactions/withdrawal" class="btn btn-default button-sm"><i class="far fa-money-bill-alt"></i> {{ $t('firefly.go_to_withdrawals') }}</a>
<a class="btn btn-default button-sm" href="./transactions/withdrawal"><i class="far fa-money-bill-alt"></i> {{ $t('firefly.go_to_withdrawals') }}</a>
</div>
</div>
</template>
<script>
import {createNamespacedHelpers} from "vuex";
const {mapState, mapGetters, mapActions, mapMutations} = createNamespacedHelpers('dashboard/index')
export default {
name: "MainDebitList",
data() {
return {
locale: 'en-US',
expenses: [],
max: 0
max: 0,
loading: true,
error: false
}
},
created() {
this.locale = localStorage.locale ?? 'en-US';
this.getExpenses();
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() {
axios.get('./api/v1/insight/expense/date/basic?start=' + window.sessionStart + '&end=' + window.sessionEnd)
this.loading = true;
this.error = false;
this.expenses = [];
let startStr = this.start.toISOString().split('T')[0];
let endStr = this.end.toISOString().split('T')[0];
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) {
@@ -83,7 +138,7 @@ export default {
this.max = data[mainKey].difference_float;
current.pct = 100;
}
if(0 !== parseInt(mainKey)) {
if (0 !== parseInt(mainKey)) {
// calc percentage:
current.pct = (data[mainKey].difference_float / this.max) * 100;
}

View File

@@ -23,7 +23,21 @@
<div class="card-header">
<h3 class="card-title">{{ $t('firefly.piggy_banks') }}</h3>
</div>
<div class="card-body table-responsive p-0">
<!-- body if loading -->
<div v-if="loading && !error" class="card-body">
<div class="text-center">
<i class="fas fa-spinner fa-spin"></i>
</div>
</div>
<!-- body if error -->
<div v-if="error" class="card-body">
<div class="text-center">
<i class="fas fa-exclamation-triangle text-danger"></i>
</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>
@@ -34,7 +48,8 @@
</thead>
<tbody>
<tr v-for="piggy in this.piggy_banks">
<td>{{ piggy.attributes.name }}
<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 }}
@@ -43,8 +58,9 @@
<td>
<div class="progress-group">
<div class="progress progress-sm">
<div class="progress-bar progress-bar-striped primary" v-if="piggy.attributes.pct < 100" :style="{'width': piggy.attributes.pct + '%'}"></div>
<div class="progress-bar progress-bar-striped bg-success" v-if="100 === piggy.attributes.pct" :style="{'width': piggy.attributes.pct + '%'}"></div>
<div v-if="piggy.attributes.pct < 100" :style="{'width': piggy.attributes.pct + '%'}" class="progress-bar progress-bar-striped 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">
@@ -65,7 +81,7 @@
</table>
</div>
<div class="card-footer">
<a href="./piggy-banks" class="btn btn-default button-sm"><i class="far fa-money-bill-alt"></i> {{ $t('firefly.go_to_piggies') }}</a>
<a class="btn btn-default button-sm" href="./piggy-banks"><i class="far fa-money-bill-alt"></i> {{ $t('firefly.go_to_piggies') }}</a>
</div>
</div>
</template>
@@ -73,17 +89,24 @@
<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;
}
);
},
computed: {
locale() {
return this.$store.getters.locale;
}
).catch(error => {
this.error = true
});
},
methods: {
loadPiggyBanks(data) {
@@ -100,11 +123,6 @@ export default {
return b.attributes.pct - a.attributes.pct;
});
}
},
data() {
return {
piggy_banks: []
}
}
}
</script>

View File

@@ -25,9 +25,11 @@
<span class="info-box-icon"><i class="far fa-bookmark text-info"></i></span>
<div class="info-box-content">
<span class="info-box-text">{{ $t("firefly.balance") }}</span>
<span v-if="!loading && !error" class="info-box-text">{{ $t("firefly.balance") }}</span>
<span v-if="loading && !error" class="info-box-text"><i class="fas fa-spinner fa-spin"></i></span>
<span v-if="error" class="info-box-text"><i class="fas fa-exclamation-triangle text-danger"></i></span>
<!-- balance in preferred currency -->
<span class="info-box-number" v-for="balance in prefCurrencyBalances" :title="balance.sub_title">{{ balance.value_parsed }}</span>
<span v-for="balance in prefCurrencyBalances" :title="balance.sub_title" class="info-box-number">{{ balance.value_parsed }}</span>
<div class="progress bg-info">
<div class="progress-bar" style="width: 0"></div>
@@ -48,10 +50,11 @@
<span class="info-box-icon"><i class="far fa-calendar-alt text-teal"></i></span>
<div class="info-box-content">
<span class="info-box-text">{{ $t('firefly.bills_to_pay') }}</span>
<span v-if="!loading && !error" class="info-box-text">{{ $t('firefly.bills_to_pay') }}</span>
<span v-if="loading && !error" class="info-box-text"><i class="fas fa-spinner fa-spin"></i></span>
<span v-if="error" class="info-box-text"><i class="fas fa-exclamation-triangle text-danger"></i></span>
<!-- bills unpaid, in preferred currency. -->
<span class="info-box-number" v-for="balance in prefBillsUnpaid">{{ balance.value_parsed }}</span>
<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>
@@ -72,10 +75,11 @@
<span class="info-box-icon"><i class="fas fa-money-bill text-success"></i></span>
<div class="info-box-content">
<span class="info-box-text">{{ $t('firefly.left_to_spend') }}</span>
<span v-if="!loading && !error" class="info-box-text">{{ $t('firefly.left_to_spend') }}</span>
<span v-if="loading && !error" class="info-box-text"><i class="fas fa-spinner fa-spin"></i></span>
<span v-if="error" class="info-box-text"><i class="fas fa-exclamation-triangle text-danger"></i></span>
<!-- left to spend in preferred currency -->
<span class="info-box-number" v-for="left in prefLeftToSpend" :title="left.sub_title">{{ left.value_parsed }}</span>
<span v-for="left in prefLeftToSpend" :title="left.sub_title" class="info-box-number">{{ left.value_parsed }}</span>
<div class="progress bg-success">
<div class="progress-bar" style="width: 0"></div>
@@ -97,8 +101,10 @@
<span class="info-box-icon"><i class="fas fa-money-bill text-success"></i></span>
<div class="info-box-content">
<span class="info-box-text"><span>{{ $t('firefly.net_worth') }}</span></span>
<span class="info-box-number" v-for="nw in prefNetWorth" :title="nw.sub_title">{{ nw.value_parsed }}</span>
<span v-if="!loading && !error" class="info-box-text">{{ $t('firefly.net_worth') }}</span>
<span v-if="loading && !error" class="info-box-text"><i class="fas fa-spinner fa-spin"></i></span>
<span v-if="error" class="info-box-text"><i class="fas fa-exclamation-triangle text-danger"></i></span>
<span v-for="nw in prefNetWorth" :title="nw.sub_title" class="info-box-number">{{ nw.value_parsed }}</span>
<div class="progress bg-success">
<div class="progress-bar" style="width: 0"></div>
@@ -117,6 +123,9 @@
</template>
<script>
import {createNamespacedHelpers} from "vuex";
const {mapState, mapGetters, mapActions, mapMutations} = createNamespacedHelpers('dashboard/index')
export default {
name: "TopBoxes",
props: {},
@@ -128,9 +137,19 @@ export default {
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 () {
@@ -170,8 +189,25 @@ export default {
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.prepareComponent();
this.ready = true;
},
methods: {
filterOnCurrency(array) {
@@ -205,11 +241,24 @@ export default {
* Prepare the component.
*/
prepareComponent() {
axios.get('./api/v1/summary/basic?start=' + window.sessionStart + '&end=' + window.sessionEnd)
this.error = false;
this.loading = true;
this.summary = [];
this.balances = [];
this.billsPaid = [];
this.billsUnpaid = [];
this.leftToSpend = [];
this.netWorth = [];
let startStr = this.start.toISOString().split('T')[0];
let endStr = this.end.toISOString().split('T')[0];
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();

View File

@@ -21,85 +21,85 @@
<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-toggle="collapse" data-parent="#accordion" 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 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>
<div class="card card-secondary">
<div class="card-header">
<h4 class="card-title">
<a data-toggle="collapse" data-parent="#accordion" 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 class="row">
<div class="col-lg-4">
A
</div>
</div>
</div>
<div class="card card-secondary">
<div class="card-header">
<h4 class="card-title">
<a data-toggle="collapse" data-parent="#accordion" 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 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>

View File

@@ -0,0 +1,37 @@
<!--
- 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>
<i v-if="'danger' === type" class="icon fas fa-ban"></i>
<i v-if="'success' === type" class="icon fas fa-thumbs-up"></i>
<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

@@ -0,0 +1,166 @@
/*
* transactions.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/>.
*/
export function getDefaultErrors() {
return {
description: [],
amount: [],
source: [],
destination: [],
currency: [],
foreign_currency: [],
foreign_amount: [],
date: [],
custom_dates: [],
budget: [],
category: [],
bill: [],
tags: [],
piggy_bank: [],
internal_reference: [],
external_url: [],
notes: [],
location: []
};
}
export function getDefaultTransaction() {
return {
// basic
description: '',
transaction_journal_id: 0,
// accounts:
source_account_id: null,
source_account_name: null,
source_account_type: null,
source_account_currency_id: null,
source_account_currency_code: null,
source_account_currency_symbol: null,
destination_account_id: null,
destination_account_name: null,
destination_account_type: null,
destination_account_currency_id: null,
destination_account_currency_code: null,
destination_account_currency_symbol: null,
source_account: {
id: 0,
name: "",
name_with_balance: "",
type: "",
currency_id: 0,
currency_name: '',
currency_code: '',
currency_decimal_places: 2
},
destination_account: {
id: 0,
name: "",
type: "",
currency_id: 0,
currency_name: '',
currency_code: '',
currency_decimal_places: 2
},
// amount:
amount: '',
currency_id: 0,
foreign_amount: '',
foreign_currency_id: 0,
// meta data
category: null,
budget_id: 0,
bill_id: 0,
piggy_bank_id: 0,
tags: [],
// optional date fields (6x):
interest_date: null,
book_date: null,
process_date: null,
due_date: null,
payment_date: null,
invoice_date: null,
// optional other fields:
internal_reference: null,
external_url: null,
external_id: null,
notes: null,
// transaction links:
links: [],
attachments: [],
// location:
zoom_level: null,
longitude: null,
latitude: null,
// error handling
errors: {},
}
}
export function toW3CString(date) {
// https://gist.github.com/tristanlins/6585391
let year = date.getFullYear();
let month = date.getMonth();
month++;
if (month < 10) {
month = '0' + month;
}
let day = date.getDate();
if (day < 10) {
day = '0' + day;
}
let hours = date.getHours();
if (hours < 10) {
hours = '0' + hours;
}
let minutes = date.getMinutes();
if (minutes < 10) {
minutes = '0' + minutes;
}
let seconds = date.getSeconds();
if (seconds < 10) {
seconds = '0' + seconds;
}
let offset = -date.getTimezoneOffset();
let offsetHours = Math.abs(Math.floor(offset / 60));
let offsetMinutes = Math.abs(offset) - offsetHours * 60;
if (offsetHours < 10) {
offsetHours = '0' + offsetHours;
}
if (offsetMinutes < 10) {
offsetMinutes = '0' + offsetMinutes;
}
let offsetSign = '+';
if (offset < 0) {
offsetSign = '-';
}
return year + '-' + month + '-' + day +
'T' + hours + ':' + minutes + ':' + seconds +
offsetSign + offsetHours + ':' + offsetMinutes;
}

View File

@@ -21,6 +21,8 @@
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';
Vue.use(Vuex)
const debug = process.env.NODE_ENV !== 'production'
@@ -31,7 +33,14 @@ export default new Vuex.Store(
transactions: {
namespaced: true,
modules: {
create: transactions_create
create: transactions_create,
edit: transactions_edit
}
},
dashboard: {
namespaced: true,
modules: {
index: dashboard_index
}
}
},
@@ -43,7 +52,7 @@ export default new Vuex.Store(
},
mutations: {
setCurrencyPreference(state, payload) {
console.log('setCurrencyPreference', payload);
//console.log('setCurrencyPreference', payload);
state.currencyPreference = payload.payload;
},
initialiseStore(state) {
@@ -77,9 +86,9 @@ export default new Vuex.Store(
actions: {
updateCurrencyPreference(context) {
if (localStorage.currencyPreference) {
console.log('set from local storage.');
console.log(localStorage.currencyPreference);
console.log({payload: JSON.parse(localStorage.currencyPreference)});
//console.log('set from local storage.');
//console.log(localStorage.currencyPreference);
//console.log({payload: JSON.parse(localStorage.currencyPreference)});
context.commit('setCurrencyPreference', {payload: JSON.parse(localStorage.currencyPreference)});
return;
}
@@ -93,8 +102,8 @@ export default new Vuex.Store(
decimal_places: parseInt(response.data.data.attributes.decimal_places),
};
localStorage.currencyPreference = JSON.stringify(currencyResponse);
console.log('getCurrencyPreference from server')
console.log(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.');

View File

@@ -0,0 +1,218 @@
/*
* 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
const state = () => (
{
viewRange: 'default',
start: null,
end: null,
// default range:
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) {
if ('default' === context.state.viewRange) {
axios.get('./api/v1/preferences/viewRange')
.then(response => {
let viewRange = response.data.data.attributes.data;
context.commit('setViewRange', viewRange);
// call another action:
context.dispatch('setDatesFromViewRange');
}
).catch(error => {
// console.log(error);
context.commit('setViewRange', '1M');
// call another action:
context.dispatch('setDatesFromViewRange');
});
}
},
setDatesFromViewRange(context) {
// console.log('Must set dates from viewRange "' + context.state.viewRange + '"');
// 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));
}
if (null !== context.getters.end && null !== context.getters.start) {
return;
}
let start;
let end;
let viewRange = context.getters.viewRange;
// console.log('Will recreate view range on ' + viewRange);
switch (viewRange) {
case '1D':
// one day:
start = new Date;
end = new Date(start.getTime());
start.setHours(0, 0, 0, 0);
end.setHours(23, 59, 59, 999);
break;
case '1W':
// this week:
start = new Date;
end = new Date(start.getTime());
// start of week
let diff = start.getDate() - start.getDay() + (start.getDay() === 0 ? -6 : 1);
start.setDate(diff);
start.setHours(0, 0, 0, 0);
// end of week
let lastday = end.getDate() - (end.getDay() - 1) + 6;
end.setDate(lastday);
end.setHours(23, 59, 59, 999);
break;
case '1M':
// this month:
start = new Date;
start = new Date(start.getFullYear(), start.getMonth(), 1);
start.setHours(0, 0, 0, 0);
end = new Date(start.getFullYear(), start.getMonth() + 1, 0);
end.setHours(23, 59, 59, 999);
break;
case '3M':
// this quarter
start = new Date;
end = new Date;
let quarter = Math.floor((start.getMonth() + 3) / 3) - 1;
// start and end months? I'm sure this could be better:
let startMonths = [0, 3, 6, 9];
let endMonths = [2, 5, 8, 11];
// set start to the correct month, day one:
start = new Date(start.getFullYear(), startMonths[quarter], 1);
start.setHours(0, 0, 0, 0);
// set end to the correct month, day one
end = new Date(end.getFullYear(), endMonths[quarter], 1);
// then to the last day of the month:
end = new Date(end.getFullYear(), end.getMonth() + 1, 0);
end.setHours(23, 59, 59, 999);
break;
case '6M':
// this half-year
start = new Date;
end = new Date;
let half = start.getMonth() <= 5 ? 0 : 1;
let startHalf = [0, 6];
let endHalf = [5, 11];
// set start to the correct month, day one:
start = new Date(start.getFullYear(), startHalf[half], 1);
start.setHours(0, 0, 0, 0);
// set end to the correct month, day one
end = new Date(end.getFullYear(), endHalf[half], 1);
// then to the last day of the month:
end = new Date(end.getFullYear(), end.getMonth() + 1, 0);
end.setHours(23, 59, 59, 999);
break;
case '1Y':
// this year
start = new Date;
end = new Date;
start = new Date(start.getFullYear(), 0, 1);
end = new Date(end.getFullYear(), 11, 31);
start.setHours(0, 0, 0, 0);
end.setHours(23, 59, 59, 999);
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;
}
}
export default {
namespaced: true,
state,
getters,
actions,
mutations
}

View File

@@ -20,15 +20,13 @@
const lodashClonedeep = require('lodash.clonedeep');
import {getDefaultTransaction, getDefaultErrors} from '../../../shared/transactions';
// initial state
const state = () => ({
transactionType: 'any',
date: new Date,
groupTitle: '',
transactions: [],
allowedOpposingTypes: {},
accountToTransaction: {},
sourceAllowedTypes: ['Asset account', 'Loan', 'Debt', 'Mortgage', 'Revenue account'],
destinationAllowedTypes: ['Asset account', 'Loan', 'Debt', 'Mortgage', 'Expense account'],
customDateFields: {
interest_date: false,
book_date: false,
@@ -37,60 +35,8 @@ const state = () => ({
payment_date: false,
invoice_date: false,
},
defaultTransaction: {
// basic
description: '',
// accounts:
source_account: {
id: 0,
name: "",
name_with_balance: "",
type: "",
currency_id: 0,
currency_name: '',
currency_code: '',
currency_decimal_places: 2
},
destination_account: {
id: 0,
name: "",
type: "",
currency_id: 0,
currency_name: '',
currency_code: '',
currency_decimal_places: 2
},
// amount:
amount: '',
currency_id: 0,
foreign_amount: '',
foreign_currency_id: 0,
// meta data
budget_id: 0,
bill_id: 0,
piggy_bank_id: 0,
tags: [],
// optional date fields (6x):
interest_date: null,
book_date: null,
process_date: null,
due_date: null,
payment_date: null,
invoice_date: null,
// optional other fields:
internal_reference: null,
external_url: null,
notes: null,
// transaction links:
links: [],
attachments: []
},
defaultTransaction: getDefaultTransaction(),
defaultErrors: getDefaultErrors()
}
)
@@ -100,12 +46,17 @@ const getters = {
transactions: state => {
return state.transactions;
},
date: state => {
return state.date;
groupTitle: state => {
return state.groupTitle;
},
transactionType: state => {
return state.transactionType;
},
accountToTransaction: state => {
// TODO better architecture here, does not need the store.
// possible API point!!
return state.accountToTransaction;
},
defaultTransaction: state => {
return state.defaultTransaction;
},
@@ -132,62 +83,35 @@ const getters = {
}
// actions
const actions = {
calcTransactionType(context) {
let source = context.state.transactions[0].source_account;
let dest = context.state.transactions[0].destination_account;
if (null === source || null === dest) {
// console.log('transactionType any');
context.commit('setTransactionType', 'any');
return;
}
if ('' === source.type || '' === dest.type) {
// console.log('transactionType any');
context.commit('setTransactionType', 'any');
return;
}
// ok so type is set on both:
let expectedDestinationTypes = context.state.accountToTransaction[source.type];
if ('undefined' !== typeof expectedDestinationTypes) {
let transactionType = expectedDestinationTypes[dest.type];
if ('undefined' !== typeof expectedDestinationTypes[dest.type]) {
// console.log('Found a type: ' + transactionType);
context.commit('setTransactionType', transactionType);
return;
}
}
// console.log('Found no type for ' + source.type + ' --> ' + dest.type);
if ('Asset account' !== source.type) {
console.log('Drop ID from source. TODO');
// source.id =null
// context.commit('updateField', {field: 'source_account',index: })
// context.state.transactions[0].source_account.id = null;
}
if ('Asset account' !== dest.type) {
console.log('Drop ID from destination. TODO');
//context.state.transactions[0].destination_account.id = null;
}
context.commit('setTransactionType', 'any');
}
}
const actions = {}
// mutations
const mutations = {
addTransaction(state) {
let newTransaction = lodashClonedeep(state.defaultTransaction);
newTransaction.errors = lodashClonedeep(state.defaultErrors);
state.transactions.push(newTransaction);
},
setDate(state, payload) {
state.date = payload.date;
resetErrors(state, payload) {
//console.log('resetErrors for index ' + payload.index);
state.transactions[payload.index].errors = lodashClonedeep(state.defaultErrors);
},
resetTransactions(state) {
state.transactions = [];
},
setGroupTitle(state, payload) {
state.groupTitle = payload.groupTitle;
},
setCustomDateFields(state, payload) {
state.customDateFields = payload;
},
deleteTransaction(state, payload) {
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;
@@ -201,6 +125,11 @@ const mutations = {
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;

View File

@@ -0,0 +1,40 @@
/*
* 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
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,687 @@
<!--
- Edit.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"/>
<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>
<!-- 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 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>
<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.after_update_create_another') }}</span>
</label>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
const lodashClonedeep = require('lodash.clonedeep');
import Alert from '../partials/Alert';
import SplitPills from "./SplitPills";
import SplitForm from "./SplitForm";
import TransactionGroupTitle from "./TransactionGroupTitle";
import {getDefaultErrors, getDefaultTransaction, toW3CString} from '../shared/transactions';
export default {
name: "Edit",
created() {
let parts = window.location.pathname.split('/');
this.groupId = parseInt(parts[parts.length - 1]);
this.getTransactionGroup();
this.getAllowedOpposingTypes();
this.getCustomFields();
},
data() {
return {
successMessage: '',
errorMessage: '',
// transaction props
transactions: [],
originalTransactions: [],
groupTitle: '',
originalGroupTitle: '',
transactionType: 'any',
groudId: 0,
// errors in the group title:
groupTitleErrors: [],
// which custom fields to show TODO
customFields: {},
// group ID + title once submitted:
returnedGroupId: 0,
returnedGroupTitle: '',
// date and time of the transaction, TODO
date: new Date,
time: new Date,
originalDate: new Date,
originalTime: new Date,
// things the process is done working on (3 phases):
submittedTransaction: false,
submittedLinks: false,
submittedAttachments: false,
// meta data for accounts
allowedOpposingTypes: {},
destinationAllowedTypes: [],
sourceAllowedTypes: [],
// states for the form (makes sense right)
enableSubmit: true,
createAnother: false,
resetFormAfter: false,
}
},
components: {
Alert,
SplitPills,
SplitForm,
TransactionGroupTitle
},
methods: {
/**
* Grap transaction group from URL and submit GET.
*/
getTransactionGroup: function () {
axios.get('./api/v1/transactions/' + this.groupId)
.then(response => {
this.parseTransactionGroup(response.data);
}
).catch(error => {
// console.log('I failed :(');
// console.log(error);
});
},
/**
* Parse transaction group. Title is easy, transactions have their own method.
* @param response
*/
parseTransactionGroup: function (response) {
// console.log('Will now parse');
// console.log(response);
let attributes = response.data.attributes;
let transactions = attributes.transactions.reverse();
this.groupTitle = attributes.group_title;
this.originalGroupTitle = attributes.group_title;
for (let i in transactions) {
if (transactions.hasOwnProperty(i) && /^0$|^[1-9]\d*$/.test(i) && i <= 4294967294) {
let result = this.parseTransaction(parseInt(i), transactions[i]);
this.transactions.push(result);
this.originalTransactions.push(lodashClonedeep(result));
// pick up the links of this transaction:
this.parseLinks(parseInt(result.transaction_journal_id), parseInt(i));
}
}
},
/**
* Parse a single transaction.
*
* @param index
* @param array
*/
parseTransaction: function (index, array) {
//console.log('index: ' + index);
if (0 === index) {
this.transactionType = array.type.charAt(0).toUpperCase() + array.type.slice(1);
this.sourceAllowedTypes = [array.source_type];
this.destinationAllowedTypes = [array.destination_type];
this.date = new Date(array.date);
this.time = new Date(array.date);
this.originalDate = new Date(array.date);
this.originalTime = new Date(array.date);
}
let result = getDefaultTransaction();
// parsing here:
result.description = array.description;
result.transaction_journal_id = parseInt(array.transaction_journal_id);
// accounts:
result.source_account_id = array.source_id;
result.source_account_name = array.source_name;
result.source_account_type = array.source_type;
result.destination_account_id = array.destination_id;
result.destination_account_name = array.destination_name;
result.destination_account_type = array.destination_type;
// amount:
result.amount = array.amount;
result.currency_id = array.currency_id;
result.foreign_amount = array.foreign_amount;
result.foreign_currency_id = array.foreign_currency_id;
// meta data
result.category = array.category_name;
result.budget_id = array.budget_id;
result.bill_id = array.bill_id;
result.tags = array.tags;
// optional date fields (6x):
result.interest_date = array.interest_date ? array.interest_date.substr(0, 10) : '';
result.book_date = array.book_date ? array.book_date.substr(0, 10) : '';
result.process_date = array.process_date ? array.process_date.substr(0, 10) : '';
result.due_date = array.due_date ? array.due_date.substr(0, 10) : '';
result.payment_date = array.payment_date ? array.payment_date.substr(0, 10) : '';
result.invoice_date = array.invoice_date ? array.invoice_date.substr(0, 10) : '';
// optional other fields:
result.internal_reference = array.internal_reference;
result.external_url = array.external_uri;
result.external_id = array.external_id;
result.notes = array.notes;
// location:
result.location = {
zoom_level: array.zoom_level,
longitude: array.longitude,
latitude: array.latitude,
};
result.zoom_level = array.zoom_level;
result.longitude = array.longitude;
result.latitude = array.latitude;
// error handling
result.errors = getDefaultErrors();
return result;
},
/**
* Get the links of this transaction group from the API.
*/
parseLinks: function (journalId, index) {
axios.get('./api/v1/transactions/' + journalId + '/links')
.then(response => {
let links = response.data.data;
for (let i in links) {
if (links.hasOwnProperty(i) && /^0$|^[1-9]\d*$/.test(i) && i <= 4294967294) {
this.parseLink(links[i], journalId, index);
}
}
});
},
/**
* Process individual link from the API.
*/
parseLink: function (link, journalId, index) {
let promises = [];
let opposingId = parseInt(link.attributes.inward_id);
let linkDirection = 'inward';
if (opposingId === journalId) {
opposingId = parseInt(link.attributes.outward_id);
linkDirection = 'outward';
}
// add meta data to promise context.
promises.push(new Promise((resolve) => {
resolve(
{
link: link,
journalId: journalId,
opposingId: opposingId,
index: index,
direction: linkDirection
}
);
}));
// get stuff from the API:
promises.push(axios.get('./api/v1/transaction-journals/' + opposingId));
promises.push(axios.get('./api/v1/transaction_links/' + link.attributes.link_type_id));
Promise.all(promises).then(responses => {
let journals = responses[1].data.data.attributes.transactions;
let opposingId = responses[0].opposingId;
let journal = {};
// loop over journals to get the correct one:
for (let i in journals) {
if (journals.hasOwnProperty(i) && /^0$|^[1-9]\d*$/.test(i) && i <= 4294967294) {
if (journals[i].transaction_journal_id === opposingId) {
journal = journals[i];
}
}
}
let index = responses[0].index;
let direction = responses[0].direction;
let linkTypeId = responses[2].data.data.id;
let object = {
link_type_id: linkTypeId + '-' + direction,
transaction_group_id: responses[1].data.data.id,
transaction_journal_id: journal.transaction_journal_id,
description: journal.description,
type: journal.type,
currency_code: journal.currency_code,
amount: journal.amount
};
this.transactions[index].links.push(object);
this.originalTransactions[index].links.push(object);
});
},
/**
* Get API value.
*/
getAllowedOpposingTypes: function () {
axios.get('./api/v1/configuration/static/firefly.allowed_opposing_types')
.then(response => {
this.allowedOpposingTypes = response.data['firefly.allowed_opposing_types'];
// console.log('Set allowedOpposingTypes');
});
},
/**
* Get API value.
*/
getCustomFields: function () {
axios.get('./api/v1/preferences/transaction_journal_optional_fields').then(response => {
this.customFields = response.data.data.attributes.data;
});
},
uploadedAttachment: function (payload) {
console.log('event: uploadedAttachment');
console.log(payload);
},
storeLocation: function (payload) {
this.transactions[payload.index].zoom_level = payload.zoomLevel;
this.transactions[payload.index].longitude = payload.lng;
this.transactions[payload.index].latitude = payload.lat;
},
storeAccountValue: function (payload) {
let direction = payload.direction;
let index = payload.index;
this.transactions[index][direction + '_account_id'] = payload.id;
this.transactions[index][direction + '_account_type'] = payload.type;
this.transactions[index][direction + '_account_name'] = payload.name;
},
storeDate: function (payload) {
// console.log('event: storeDate');
// console.log(payload);
this.date = payload.date;
},
storeTime: function (payload) {
this.time = payload.time;
// console.log('event: storeTime');
// console.log(payload);
},
storeField: function (payload) {
let field = payload.field;
if ('category' === field) {
field = 'category_name';
}
// console.log('event: storeField(' + field + ')');
this.transactions[payload.index][field] = payload.value;
},
removeTransaction: function (payload) {
this.transactions.splice(payload.index, 1);
// this kills the original transactions.
this.originalTransactions = [];
},
storeGroupTitle: function (payload) {
this.groupTitle = payload;
},
selectedAttachments: function (payload) {
for (let i in this.transactions) {
if (this.transactions.hasOwnProperty(i) && /^0$|^[1-9]\d*$/.test(i) && i <= 4294967294) {
if (parseInt(this.transactions[i].transaction_journal_id) === parseInt(payload)) {
// console.log('selectedAttachments ' + payload);
this.transactions[i].selectedAttachments = true;
}
}
}
},
addTransaction: function () {
let newTransaction = getDefaultTransaction();
newTransaction.errors = getDefaultErrors();
this.transactions.push(newTransaction);
},
submitTransaction: function () {
let submission = {transactions: []};
let shouldSubmit = false;
let shouldLinks = false;
let shouldUpload = false;
if (this.groupTitle !== this.originalGroupTitle) {
submission.group_title = this.groupTitle;
shouldSubmit = true;
}
for (let i in this.transactions) {
if (this.transactions.hasOwnProperty(i) && /^0$|^[1-9]\d*$/.test(i) && i <= 4294967294) {
// original transaction present?
let currentTransaction = this.transactions[i];
let originalTransaction = this.originalTransactions.hasOwnProperty(i) ? this.originalTransactions[i] : {};
let diff = {};
// compare basic fields:
let basicFields = [
'description',
'source_account_id', 'source_account_name',
'destination_account_id', 'destination_account_name',
'amount', 'foreign_amount', 'foreign_currency_id',
'category_name', 'budget_id', 'bill_id',
'interest_date', 'book_date', 'due_date', 'payment_date', 'invoice_date',
'external_url', 'internal_reference', 'external_id', 'notes',
'zoom_level', 'longitude', 'latitude'
];
for (let ii in basicFields) {
if (basicFields.hasOwnProperty(ii) && /^0$|^[1-9]\d*$/.test(ii) && ii <= 4294967294) {
let fieldName = basicFields[ii];
if (currentTransaction[fieldName] !== originalTransaction[fieldName]) {
// console.log('Index ' + i + ': Field ' + fieldName + ' updated ("' + originalTransaction[fieldName] + '" > "' + currentTransaction[fieldName] + '")');
// console.log(originalTransaction[fieldName]);
// console.log(currentTransaction[fieldName]);
diff[fieldName] = currentTransaction[fieldName];
}
}
}
if (0 !== currentTransaction.piggy_bank_id) {
diff.piggy_bank_id = currentTransaction.piggy_bank_id;
}
if (JSON.stringify(currentTransaction.tags) !== JSON.stringify(originalTransaction.tags)) {
// console.log('tags are different');
// console.log(currentTransaction.tags);
// console.log(originalTransaction.tags);
diff.tags = currentTransaction.tags;
}
// compare links:
let newLinks = this.compareLinks(currentTransaction.links);
let originalLinks = this.compareLinks(originalTransaction.links);
// console.log('links are?');
// console.log(newLinks);
// console.log(originalLinks);
if (newLinks !== originalLinks) {
// console.log('links are different!');
// console.log(newLinks);
// console.log(originalLinks);
shouldLinks = true;
}
// this.transactions[i].selectedAttachments
// console.log(typeof currentTransaction.selectedAttachments);
// console.log(currentTransaction.selectedAttachments);
if (typeof currentTransaction.selectedAttachments !== 'undefined' && true === currentTransaction.selectedAttachments) {
// must upload!
shouldUpload = true;
}
let dateStr = 'invalid';
if (
this.date.toISOString() !== this.originalDate.toISOString() ||
this.time.toISOString() !== this.originalTime.toISOString()
) {
// set date and time!
shouldSubmit = true;
let theDate = this.date;
// update time in date object.
theDate.setHours(this.time.getHours());
theDate.setMinutes(this.time.getMinutes());
theDate.setSeconds(this.time.getSeconds());
dateStr = toW3CString(theDate);
submission.date = dateStr;
}
if (Object.keys(diff).length !== 0) {
diff.transaction_journal_id = originalTransaction.transaction_journal_id;
submission.transactions.push(diff);
shouldSubmit = true;
}
}
}
console.log('submitTransaction');
console.log(shouldUpload);
console.log(shouldLinks);
console.log(shouldSubmit);
if (shouldSubmit) {
this.submitUpdate(submission);
}
//console.log(submission);
},
compareLinks: function (array) {
let compare = [];
for (let i in array) {
if (array.hasOwnProperty(i) && /^0$|^[1-9]\d*$/.test(i) && i <= 4294967294) {
compare.push(
{
amount: array[i].amount,
currency_code: array[i].currency_code,
description: array[i].description,
link_type_id: array[i].link_type_id,
transaction_group_id: array[i].transaction_group_id,
type: array[i].type,
}
);
}
}
// console.log('compareLinks');
// console.log(compare);
return JSON.stringify(compare);
},
submitUpdate: function (submission) {
console.log('submitUpdate');
const url = './api/v1/transactions/' + this.groupId;
axios.put(url, submission)
.then(response => {
// console.log('Response is OK!');
// report the transaction is submitted.
this.submittedTransaction = true;
// // submit links and attachments (can only be done when the transaction is created)
// this.submitTransactionLinks(data, response);
// this.submitAttachments(data, response);
//
// // meanwhile, store the ID and the title in some easy to access variables.
// 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;
}
)
.catch(error => {
console.log('error :(');
console.log(error.response.data);
// oh noes Firefly III has something to bitch about.
this.enableSubmit = true;
// report the transaction is submitted.
this.submittedTransaction = true;
// // also report attachments and links are submitted:
this.submittedAttachments = true;
this.submittedLinks = true;
//
// but report an error because error:
this.inError = true;
this.parseErrors(error.response.data);
}
);
},
parseErrors: function (errors) {
for (let i in this.transactions) {
if (this.transactions.hasOwnProperty(i) && /^0$|^[1-9]\d*$/.test(i) && i <= 4294967294) {
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));
}
}
}
},
setTransactionError: function (payload) {
this.transactions[payload.index].errors[payload.field] = payload.errors;
},
resetErrors(payload) {
this.transactions[payload.index].errors = lodashClonedeep(getDefaultErrors());
},
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,459 @@
<!--
- 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 class="btn btn-danger btn-xs" @click="removeTransaction"><i class="fas fa-trash-alt"></i></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"
:allowed-opposing-types="allowedOpposingTypes"
:destination-allowed-types="destinationAllowedTypes"
:errors="transaction.errors.source"
:index="index"
:source-allowed-types="sourceAllowedTypes"
direction="source"
/>
</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"
:allowed-opposing-types="allowedOpposingTypes"
:destination-allowed-types="destinationAllowedTypes"
:errors="transaction.errors.destination"
:index="index"
: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"
: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"
:time="splitTime"
/>
</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"
: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"
:submitted_transaction="submittedTransaction"
:transaction_journal_id="transaction.transaction_journal_id"
/>
<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: false
},
customFields: {
type: Object,
required: false
},
index: {
type: Number,
required: true
},
date: {
type: Date,
required: true
},
time: {
type: Date,
required: true
},
transactionType: {
type: String,
required: true
},
submittedTransaction: {
type: Boolean,
required: false,
default: false
}, // need to know if transaction is submitted.
sourceAllowedTypes: {
type: Array,
required: false,
default: []
}, // allowed source account types.
destinationAllowedTypes: {
type: Array,
required: false,
default: []
},
allowedOpposingTypes: {
type: Object,
required: false,
default: {}
},
// allow switch?
allowSwitch: {
type: Boolean,
required: false,
default: true
}
},
// watch: {
// allowedOpposingTypes: function() {
// console.log('SplitForm noticed change in allowedOpposingTypes');
// }
// },
methods: {
removeTransaction: function () {
// console.log('Will remove transaction ' + this.index);
this.$emit('remove-transaction', {index: this.index});
},
},
computed: {
splitDate: function () {
return this.date;
},
splitTime: function () {
return this.time;
},
sourceAccount: function () {
return {
id: this.transaction.source_account_id,
name: this.transaction.source_account_name,
type: this.transaction.source_account_type,
};
},
destinationAccount: function () {
return {
id: this.transaction.destination_account_id,
name: this.transaction.destination_account_name,
type: this.transaction.destination_account_type,
};
},
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

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

View File

@@ -21,10 +21,10 @@
<template>
<div class="form-group">
<div class="text-xs d-none d-lg-block d-xl-block">
<span class="text-muted" v-if="'any' !== this.transactionType">
<span v-if="'any' !== this.transactionType" class="text-muted">
{{ $t('firefly.' + this.transactionType) }}
</span>
<span class="text-muted" v-if="'any' === this.transactionType">&nbsp;</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>
@@ -33,34 +33,13 @@
</template>
<script>
import {createNamespacedHelpers} from "vuex";
const {mapState, mapGetters, mapActions, mapMutations} = createNamespacedHelpers('transactions/create')
export default {
name: "SwitchAccount",
props: ['index'],
props: ['index', 'transactionType'],
methods: {
...mapMutations(
[
'updateField',
],
),
switchAccounts() {
let source = this.transactions[this.index].source_account;
let dest = this.transactions[this.index].destination_account;
this.updateField({field: 'source_account', index: this.index, value: dest});
this.updateField({field: 'destination_account', index: this.index, value: source});
// trigger other components.
this.$emit('switch-accounts', this.index);
}
},
computed: {
...mapGetters(['transactions', 'transactionType']),
}
}
</script>

View File

@@ -20,28 +20,44 @@
<template>
<div class="form-group">
<div class="text-xs d-none d-lg-block d-xl-block">
{{ $t('firefly.' + this.direction + '_account') }}
<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-model="value.name"
v-if="visible"
v-model="accountName"
:data="accounts"
:showOnFocus=true
:inputClass="errors.length > 0 ? 'is-invalid' : ''"
:inputName="direction + '[]'"
:serializer="item => item.name_with_balance"
:minMatchingChars="3"
:placeholder="$t('firefly.' + this.direction + '_account')"
@input="lookupAccount"
:placeholder="$t('firefly.' + direction + '_account')"
:serializer="item => item.name_with_balance"
:showOnFocus=true
@hit="selectedAccount = $event"
@input="lookupAccount"
>
<template slot="append">
<div class="input-group-append">
<button class="btn btn-outline-secondary" v-on:click="clearAccount" type="button"><i class="far fa-trash-alt"></i></button>
<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"><i class="far fa-trash-alt"></i></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>
@@ -49,80 +65,131 @@
import VueTypeaheadBootstrap from 'vue-typeahead-bootstrap';
import {debounce} from 'lodash';
import {createNamespacedHelpers} from "vuex";
const {mapState, mapGetters, mapActions, mapMutations} = createNamespacedHelpers('transactions/create')
export default {
name: "TransactionAccount",
components: {VueTypeaheadBootstrap},
props: ['index', 'direction', 'value'],
props: [
'index',
'direction',
'value',
'errors',
'sourceAllowedTypes',
'destinationAllowedTypes',
'allowedOpposingTypes'
],
data() {
return {
query: '',
accounts: [],
accountTypes: [],
initialSet: [],
selectedAccount: {}
selectedAccount: {},
account: this.value,
accountName: '',
selectedAccountTrigger: false,
}
},
created() {
this.selectedAccountTrigger = true;
this.accountName = this.account.name ?? '';
this.createInitialSet();
},
methods: {
...mapMutations(
[
'updateField',
'setDestinationAllowedTypes',
'setSourceAllowedTypes'
],
),
...mapActions(
[
'calcTransactionType'
]
),
getACURL: function (types, query) {
return document.getElementsByTagName('base')[0].href + 'api/v1/autocomplete/accounts?types=' + types.join(',') + '&query=' + query;
return './api/v1/autocomplete/accounts?types=' + types.join(',') + '&query=' + query;
},
clearAccount: function () {
this.accounts = this.initialSet;
this.value = {name: ''};
this.account = {name: '', type: 'no_type', id: null, currency_id: null, currency_code: null, currency_symbol: null};
this.accountName = '';
},
lookupAccount: debounce(function () {
//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.value.name))
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 () {
let types = this.sourceAllowedTypes;
if ('destination' === this.direction) {
types = this.destinationAllowedTypes;
}
// console.log(this.direction + ' initial set searches for');
// console.log(types);
axios.get(this.getACURL(types, ''))
.then(response => {
// console.log('initial set of accounts. ' + this.direction);
this.accounts = response.data;
this.initialSet = response.data;
});
}
},
watch: {
selectedAccount: function (value) {
this.value = value;
this.value.name = this.value.name_with_balance;
// 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);
this.createInitialSet();
},
value: function (value) {
this.updateField({field: this.accountKey, index: this.index, value: value});
// set the opposing account allowed set.
destinationAllowedTypes: function (value) {
// console.log(this.direction + ' account noticed change in destinationAllowedTypes');
// console.log(value);
this.createInitialSet();
},
selectedAccount: function (value) {
this.selectedAccountTrigger = true;
this.account = value;
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,
}
);
this.accountName = this.account.name_with_balance;
},
accountName: function (value) {
if (false === this.selectedAccountTrigger) {
// console.log('Save to change name!');
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,
}
);
this.accountTrigger = false;
this.account = {name: value, type: null, id: null, currency_id: null, currency_code: null, currency_symbol: null};
}
this.selectedAccountTrigger = false;
},
account: function (value) {
let opposingAccounts = [];
let type = value.type ? value.type : 'no_type';
if ('undefined' !== typeof this.allowedOpposingTypes[this.direction]) {
@@ -132,91 +199,46 @@ export default {
}
if ('source' === this.direction) {
this.setDestinationAllowedTypes(opposingAccounts);
this.$emit('set-dest-types', opposingAccounts);
}
if ('destination' === this.direction) {
this.setSourceAllowedTypes(opposingAccounts);
this.$emit('set-src-types', opposingAccounts);
}
this.calcTransactionType();
},
// account: function (value) {
// //this.value.name = value;
// //console.log('watch account in direction ' + this.direction + ' change to "' + value + '"');
// // this.account = value ? value.name_with_balance : null;
// // // console.log('this.account (' + this.direction + ') = "' + this.account + '"');
// //
// //
// // // set the opposing account allowed set.
// // // console.log('opposing:');
// // 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.setDestinationAllowedTypes(opposingAccounts);
// // }
// // if ('destination' === this.direction) {
// // this.setSourceAllowedTypes(opposingAccounts);
// // }
//
//
// //
// // this.calcTransactionType();
//
//
// }
// selectedAccount: function (value) {
// },
// sourceAllowedTypes: function (value) {
// if ('source' === this.direction) {
// // console.log('do update initial set in direction ' + this.direction + ' because allowed types changed');
// // update initial set:
// this.createInitialSet();
// }
// },
// destinationAllowedTypes: function (value) {
// if ('destination' === this.direction) {
// // console.log('do update initial set in direction ' + this.direction + ' because allowed types changed');
// // update initial set:
// this.createInitialSet();
// }
// }
value: function (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 ?? '';
}
},
computed: {
...mapGetters([
'transactionType',
'transactions',
'defaultTransaction',
'sourceAllowedTypes',
'destinationAllowedTypes',
'allowedOpposingTypes'
]),
// 'transactionType',
// 'sourceAllowedTypes',
// 'destinationAllowedTypes',
// 'allowedOpposingTypes'
accountKey: {
get() {
return 'source' === this.direction ? 'source_account' : 'destination_account';
}
},
// selectedAccount: {
// get() {
// return this.transactions[this.index][this.accountKey];
// },
// set(value) {
// // console.log('set selectedAccount for ' + this.direction);
// // console.log(value);
// this.updateField({field: this.accountKey, index: this.index, value: value});
// }
// }
visible: {
get() {
// index 0 is always visible:
if (0 === this.index) {
return true;
}
if ('source' === this.direction) {
return 'any' === this.transactionType || 'Deposit' === this.transactionType
}
if ('destination' === this.direction) {
return 'any' === this.transactionType || 'Withdrawal' === this.transactionType;
}
return false;
}
}
}
}
</script>
<style scoped>
</style>

View File

@@ -22,134 +22,92 @@
<div class="form-group">
<div class="text-xs">{{ $t('firefly.amount') }}</div>
<div class="input-group">
<div class="input-group-prepend">
<div v-if="currencySymbol" class="input-group-prepend">
<div class="input-group-text">{{ currencySymbol }}</div>
</div>
<input type="hidden" name="currency_id[]" :value="currencyId"/>
<input
v-model="transactionAmount"
:class="errors.length > 0 ? 'form-control is-invalid' : 'form-control'"
:placeholder="$t('firefly.amount')"
:title="$t('firefly.amount')"
autocomplete="off"
class="form-control"
name="amount[]"
type="number"
v-model="amount"
:placeholder="$t('firefly.amount')"
>
</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 {createNamespacedHelpers} from "vuex";
const {mapState, mapGetters, mapActions, mapMutations} = createNamespacedHelpers('transactions/create')
//const {mapRootState, mapRootGetters, mapRootActions, mapRootMutations} = createHelpers('');
export default {
name: "TransactionAmount",
props: ['index'],
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);
}
},
methods: {
formatNumber(str) {
return parseFloat(str).toFixed(this.fractionDigits);
}
},
data() {
return {
currencySymbol: ''
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':
// take currency from source:
this.currencyId = this.transactions[this.index].source_account.currency_id;
this.currencySymbol = this.transactions[this.index].source_account.currency_symbol;
return;
this.currencySymbol = this.srcCurrencySymbol;
break;
case 'Deposit':
// take currency from destination:
this.currencyId = this.transactions[this.index].destination_account.currency_id;
this.currencySymbol = this.transactions[this.index].destination_account.currency_symbol;
return;
this.currencySymbol = this.dstCurrencySymbol;
}
},
destinationAllowedTypes: function (value) {
// aka source was updated. if source is asset/loan/debt/mortgage use it to set the currency:
if ('undefined' !== typeof this.transactions[this.index].source_account.type) {
if (['Asset account', 'Loan', 'Debt', 'Mortgage'].indexOf(this.transactions[this.index].source_account.type) !== -1) {
// get currency pref from source account
this.currencyId = this.transactions[this.index].source_account.currency_id;
this.currencySymbol = this.transactions[this.index].source_account.currency_symbol;
}
}
},
sourceAllowedTypes: function (value) {
// aka destination was updated. if destination is asset/loan/debt/mortgage use it to set the currency:
// unless its already known to be a transfer
if ('undefined' !== typeof this.transactions[this.index].destination_account.type && 'Transfer' !== this.transactionType) {
if (['Asset account', 'Loan', 'Debt', 'Mortgage'].indexOf(this.transactions[this.index].destination_account.type) !== -1) {
// get currency pref from destination account
this.currencyId = this.transactions[this.index].destination_account.currency_id;
this.currencySymbol = this.transactions[this.index].destination_account.currency_symbol;
}
}
},
},
created: function () {
this.updateCurrency();
},
methods: {
...mapMutations(
[
'updateField',
],
),
updateCurrency: function () {
if (0 === this.currencyId) {
// use default currency from store.
this.currencySymbol = this.currencyPreference.symbol;
this.currencyId = this.currencyPreference.id;
}
}
},
computed: {
currencyPreference: {
get() {
return this.$store.state.currencyPreference;
}
},
...mapGetters([
'transactionType',
'transactions',
'destinationAllowedTypes',
'sourceAllowedTypes',
]),
amount: {
get() {
return this.transactions[this.index].amount;
},
set(value) {
this.updateField({field: 'amount', index: this.index, value: value});
}
},
currencyId: {
get() {
return this.transactions[this.index].currency_id;
},
set(value) {
this.updateField({field: 'currency_id', index: this.index, value: value});
}
},
selectedTransactionType: {
get() {
return this.transactionType;
},
set(value) {
// console.log('set selectedAccount for ' + this.direction);
// console.log(value);
// this.updateField({field: this.accountKey, index: this.index, value: value});
}
}
}
}
</script>

View File

@@ -19,24 +19,102 @@
-->
<template>
<div class="form-group">
<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
type="file"
ref="att"
class="form-control"
multiple
@change="selectedFile"
name="attachments[]"
:placeholder="$t('firefly.attachment')"
class="form-control"/>
type="file"
/>
</div>
</div>
</template>
<script>
export default {
name: "TransactionAttachments"
name: "TransactionAttachments",
props: ['transaction_journal_id', 'customFields'],
data() {
return {
availableFields: this.customFields
}
},
watch: {
customFields: function (value) {
this.availableFields = value;
},
transaction_journal_id: function (value) {
if (!this.showField) {
// console.log('Field is hidden. Emit event!');
this.$emit('uploaded-attachments', value);
return;
}
// console.log('transaction_journal_id changed to ' + value);
// do upload!
if (0 !== value) {
this.doUpload();
}
}
},
computed: {
showField: function () {
if ('attachments' in this.availableFields) {
return this.availableFields.attachments;
}
return false;
}
},
methods: {
selectedFile: function() {
this.$emit('selected-attachments', this.transaction_journal_id);
},
doUpload: function () {
// console.log('Now in doUpload() for ' + this.$refs.att.files.length + ' files.');
for (let i in this.$refs.att.files) {
if (this.$refs.att.files.hasOwnProperty(i) && /^0$|^[1-9]\d*$/.test(i) && i <= 4294967294) {
let current = this.$refs.att.files[i];
let fileReader = new FileReader();
let theParent = this; // dont ask me why i need to do this.
fileReader.onloadend = function (evt) {
if (evt.target.readyState === FileReader.DONE) {
// do upload here
const uri = './api/v1/attachments';
const data = {
filename: current.name,
attachable_type: 'TransactionJournal',
attachable_id: theParent.transaction_journal_id,
};
// create new attachment:
axios.post(uri, data).then(response => {
// upload actual file:
const uploadUri = './api/v1/attachments/' + response.data.data.id + '/upload';
axios
.post(uploadUri, new Blob([evt.target.result]))
.then(attachmentResponse => {
// TODO feedback etc.
// console.log('Uploaded a file. Emit event!');
// console.log(attachmentResponse);
theParent.$emit('uploaded-attachments', this.transaction_journal_id);
});
});
}
}
fileReader.readAsArrayBuffer(current);
}
}
if (0 === this.$refs.att.files.length) {
// console.log('No files to upload. Emit event!');
this.$emit('uploaded-attachments', this.transaction_journal_id);
}
}
}
}
</script>

View File

@@ -26,43 +26,38 @@
<div class="input-group">
<select
ref="bill"
v-model="bill"
:class="errors.length > 0 ? 'form-control is-invalid' : 'form-control'"
:title="$t('firefly.bill')"
v-model="value"
autocomplete="off"
class="form-control"
name="bill_id[]"
v-on:submit.prevent
>
<option v-for="bill in this.billList" :value="bill.id" :label="bill.name">{{ bill.name }}</option>
<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>
import {createNamespacedHelpers} from "vuex";
const {mapState, mapGetters, mapActions, mapMutations} = createNamespacedHelpers('transactions/create')
export default {
props: ['value', 'index'],
props: ['value', 'index', 'errors'],
name: "TransactionBill",
data() {
return {
billList: []
billList: [],
bill: this.value,
emitEvent: true
}
},
created() {
this.collectData();
},
methods: {
...mapMutations(
[
'updateField',
],
),
collectData() {
this.billList.push(
{
@@ -94,18 +89,17 @@ export default {
},
},
watch: {
value: function(value) {
this.updateField({field: 'bill_id', index: this.index, value: value});
value: function (value) {
this.emitEvent = false;
this.bill = value;
},
bill: function (value) {
if (true === this.emitEvent) {
this.$emit('set-field', {field: 'bill_id', index: this.index, value: value});
}
this.emitEvent = true;
}
},
computed: {
...mapGetters(
[
'transactionType',
'transactions',
]
)
}
}
</script>

View File

@@ -26,42 +26,37 @@
<div class="input-group">
<select
ref="budget"
v-model="budget"
:class="errors.length > 0 ? 'form-control is-invalid' : 'form-control'"
:title="$t('firefly.budget')"
v-model="value"
autocomplete="off"
class="form-control"
name="budget_id[]"
v-on:submit.prevent
>
<option v-for="budget in this.budgetList" :value="budget.id" :label="budget.name">{{ budget.name }}</option>
<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>
import {createNamespacedHelpers} from "vuex";
const {mapState, mapGetters, mapActions, mapMutations} = createNamespacedHelpers('transactions/create')
export default {
props: ['index', 'value'],
props: ['index', 'value', 'errors'],
name: "TransactionBudget",
data() {
return {
budgetList: []
budgetList: [],
budget: this.value,
emitEvent: true
}
},
created() {
this.collectData();
},
methods: {
...mapMutations(
[
'updateField',
],
),
collectData() {
this.budgetList.push(
{
@@ -94,16 +89,12 @@ export default {
},
watch: {
value: function (value) {
this.updateField({field: 'budget_id', index: this.index, value: value});
this.emitEvent = false;
this.budget = value;
},
budget: function (value) {
this.$emit('set-field', {field: 'budget_id', index: this.index, value: value});
}
},
computed: {
...mapGetters(
[
'transactionType',
'transactions',
]
)
}
}
</script>

View File

@@ -25,42 +25,44 @@
</div>
<vue-typeahead-bootstrap
inputName="category[]"
v-model="value"
v-model="category"
:data="categories"
:placeholder="$t('firefly.category')"
:showOnFocus=true
: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"
>
<template slot="append">
<div class="input-group-append">
<button v-on:click="clearCategory" class="btn btn-outline-secondary" type="button"><i class="far fa-trash-alt"></i></button>
<button class="btn btn-outline-secondary" tabindex="-1" type="button" v-on:click="clearCategory"><i class="far fa-trash-alt"></i></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 {createNamespacedHelpers} from "vuex";
import VueTypeaheadBootstrap from 'vue-typeahead-bootstrap';
import {debounce} from "lodash";
const {mapState, mapGetters, mapActions, mapMutations} = createNamespacedHelpers('transactions/create')
export default {
props: ['value', 'index'],
props: ['value', 'index', 'errors'],
components: {VueTypeaheadBootstrap},
name: "TransactionCategory",
data() {
return {
categories: [],
initialSet: []
initialSet: [],
category: this.value,
emitEvent: true
}
},
@@ -75,13 +77,8 @@ export default {
},
methods: {
...mapMutations(
[
'updateField',
],
),
clearCategory: function () {
this.value = '';
this.category = '';
},
getACURL: function (query) {
// update autocomplete URL:
@@ -97,22 +94,20 @@ export default {
},
watch: {
value: function (value) {
this.updateField({field: 'category', index: this.index, value: value});
this.emitEvent = false;
this.category = value ?? '';
},
category: function (value) {
this.$emit('set-field', {field: 'category', index: this.index, value: value});
}
},
computed: {
...mapGetters(
[
'transactionType',
'transactions',
]
),
selectedCategory: {
get() {
return this.categories[this.index].name;
},
set(value) {
this.value = value.name;
this.category = value.name;
}
}
}

View File

@@ -20,21 +20,21 @@
<template>
<div>
<div class="form-group" v-for="(enabled, name) in enabledDates">
<div class="text-xs d-none d-lg-block d-xl-block" v-if="enabled">
<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 class="input-group" v-if="enabled">
<div v-if="enabled && isDateField(name)" class="input-group">
<input
class="form-control"
type="date"
:ref="name"
:title="$t('form.' + name)"
:value="getFieldValue(name)"
@change="setFieldValue($event, name)"
autocomplete="off"
:name="name + '[]'"
:placeholder="$t('form.' + name)"
:title="$t('form.' + name)"
:value="getFieldValue(name)"
autocomplete="off"
class="form-control"
type="date"
@change="setFieldValue($event, name)"
v-on:submit.prevent
>
</div>
@@ -43,33 +43,67 @@
</template>
<script>
import {createNamespacedHelpers} from "vuex";
const {mapState, mapGetters, mapActions, mapMutations} = createNamespacedHelpers('transactions/create')
export default {
name: "TransactionCustomDates",
props: ['enabledDates', 'index'],
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: {
...mapGetters(
[
'transactions'
]
),
...mapMutations(
[
'updateField',
],
),
isDateField: function (name) {
return this.dateFields.includes(name)
},
getFieldValue(field) {
return this.transactions()[parseInt(this.index)][field] ?? '';
return this.dates[field] ?? '';
},
setFieldValue(event, field) {
this.updateField({index: this.index, field: field, value: event.target.value});
}
this.$emit('set-field', {field: field, index: this.index, value: event.target.value});
},
}
}
</script>
<style scoped>
</style>

View File

@@ -25,87 +25,96 @@
</div>
<div class="input-group">
<input
class="form-control"
type="date"
ref="date"
:title="$t('firefly.date')"
v-model="localDate"
v-model="dateStr"
:class="errors.length > 0 ? 'form-control is-invalid' : 'form-control'"
:disabled="index > 0"
:placeholder="dateStr"
:title="$t('firefly.date')"
autocomplete="off"
name="date[]"
:placeholder="localDate"
v-on:submit.prevent
type="date"
>
<input
class="form-control"
type="time"
ref="time"
:title="$t('firefly.time')"
v-model="localTime"
v-model="timeStr"
:class="errors.length > 0 ? 'form-control is-invalid' : 'form-control'"
:disabled="index > 0"
:placeholder="timeStr"
:title="$t('firefly.time')"
autocomplete="off"
name="time[]"
:placeholder="localTime"
v-on:submit.prevent
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" v-if="'' !== timeZone">{{ timeZone }}</span>
</div>
</template>
<script>
import {createNamespacedHelpers} from "vuex";
const {mapState, mapGetters, mapActions, mapMutations} = createNamespacedHelpers('transactions/create')
export default {
props: ['index', 'errors', 'date', 'time'],
name: "TransactionDate",
props: ['index'],
methods: {
...mapMutations(
[
'updateField',
'setDate'
],
),
created() {
this.timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
},
data() {
return {
localDate: this.date,
localTime: this.time,
timeZone: ''
}
},
methods: {},
computed: {
...mapGetters([
'transactionType',
'date'
]),
localDate: {
dateStr: {
get() {
return this.date.toISOString().split('T')[0];
if (this.localDate instanceof Date && !isNaN(this.localDate)) {
return this.localDate.toISOString().split('T')[0];
}
return '';
},
set(value) {
// bit of a hack but meh.
let newDate = new Date(value);
let current = new Date(this.date.getTime());
current.setFullYear(newDate.getFullYear());
current.setMonth(newDate.getMonth());
current.setDate(newDate.getDate());
this.setDate({date: current});
if ('' === value) {
// reset to today
this.localDate = new Date();
this.$emit('set-date', {date: this.localDate});
return;
}
this.localDate = new Date(value);
this.$emit('set-date', {date: this.localDate});
}
},
localTime: {
timeStr: {
get() {
return ('0' + this.date.getHours()).slice(-2) + ':' + ('0' + this.date.getMinutes()).slice(-2) + ':' + ('0' + this.date.getSeconds()).slice(-2);
if (this.localTime instanceof Date && !isNaN(this.localTime)) {
return ('0' + this.localTime.getHours()).slice(-2) + ':' + ('0' + this.localTime.getMinutes()).slice(-2) + ':' + ('0' + this.localTime.getSeconds()).slice(-2);
}
return '';
},
set(value) {
if ('' === value) {
this.localTime.setHours(0);
this.localTime.setMinutes(0);
this.localTime.setSeconds(0);
this.$emit('set-time', {time: this.localTime});
return;
}
// bit of a hack but meh.
let current = new Date(this.date.getTime());
let current = new Date(this.localTime.getTime());
let parts = value.split(':');
current.setHours(parseInt(parts[0]));
current.setMinutes(parseInt(parts[1]));
current.setSeconds(parseInt(parts[2]));
this.setDate({date: current});
this.localTime = current;
this.$emit('set-time', {time: this.localTime});
}
}
}
}
</script>
<style scoped>
</style>

View File

@@ -20,50 +20,46 @@
<template>
<div class="form-group">
<div class="text-xs d-none d-lg-block d-xl-block">
{{ $t('firefly.description') }}
</div>
<vue-typeahead-bootstrap
inputName="description[]"
v-model="value"
v-model="description"
:data="descriptions"
:inputClass="errors.length > 0 ? 'is-invalid' : ''"
:minMatchingChars="3"
:placeholder="$t('firefly.description')"
:serializer="item => item.description"
:showOnFocus=true
autofocus
:minMatchingChars="3"
:serializer="item => item.description"
inputName="description[]"
@input="lookupDescription"
>
<template slot="append">
<div class="input-group-append">
<button v-on:click="clearDescription" class="btn btn-outline-secondary" type="button"><i class="far fa-trash-alt"></i></button>
<button class="btn btn-outline-secondary" tabindex="-1" type="button" v-on:click="clearDescription"><i class="far fa-trash-alt"></i></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 {createNamespacedHelpers} from "vuex";
import VueTypeaheadBootstrap from 'vue-typeahead-bootstrap';
import {debounce} from "lodash";
const {mapState, mapGetters, mapActions, mapMutations} = createNamespacedHelpers('transactions/create')
export default {
props: ['index', 'value'],
props: ['index', 'value', 'errors'],
components: {VueTypeaheadBootstrap},
name: "TransactionDescription",
data() {
return {
descriptions: [],
initialSet: []
initialSet: [],
description: this.value,
}
},
created() {
axios.get(this.getACURL(''))
.then(response => {
@@ -73,13 +69,8 @@ export default {
},
methods: {
...mapMutations(
[
'updateField',
],
),
clearDescription: function () {
this.value = '';
this.description = '';
},
getACURL: function (query) {
// update autocomplete URL:
@@ -95,16 +86,11 @@ export default {
},
watch: {
value: function (value) {
this.updateField({field: 'description', index: this.index, value: value});
this.description = value;
},
description: function (value) {
this.$emit('set-field', {field: 'description', index: this.index, value: value});
}
},
computed: {
...mapGetters(
[
'transactionType',
'transactions',
]
)
}
}
</script>

View File

@@ -19,42 +19,53 @@
-->
<template>
<div class="form-group">
<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
type="url"
name="external_url[]"
v-model="url"
:class="errors.length > 0 ? 'form-control is-invalid' : 'form-control'"
:placeholder="$t('firefly.external_url')"
v-model="value"
class="form-control"/>
name="external_url[]"
type="url"
/>
<div class="input-group-append">
<button type="button" class="btn btn-outline-secondary"><i class="far fa-trash-alt"></i></button>
<button class="btn btn-outline-secondary" tabindex="-1" type="button"><i class="far fa-trash-alt"></i></button>
</div>
</div>
</div>
</template>
<script>
import {createNamespacedHelpers} from "vuex";
const {mapState, mapGetters, mapActions, mapMutations} = createNamespacedHelpers('transactions/create')
export default {
props: ['index', 'value'],
props: ['index', 'value', 'errors', 'customFields'],
name: "TransactionExternalUrl",
methods: {
...mapMutations(
[
'updateField',
],
),
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.updateField({field: 'external_url', index: this.index, value: value});
this.url = value;
},
url: function (value) {
this.$emit('set-field', {field: 'external_url', index: this.index, value: value});
}
}
}

View File

@@ -20,136 +20,76 @@
<template>
<!-- FOREIGN AMOUNT -->
<div class="form-group">
<input type="hidden" name="foreign_currency_id[]" :value="currencyId"/>
<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"
class="form-control"
:disabled="0===currencyId"
name="foreign_amount[]"
type="number"
v-model="amount"
:placeholder="$t('form.foreign_amount')"
>
</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 {createNamespacedHelpers} from "vuex";
const {mapState, mapGetters, mapActions, mapMutations} = createNamespacedHelpers('transactions/create')
//const {mapRootState, mapRootGetters, mapRootActions, mapRootMutations} = createHelpers('');
export default {
name: "TransactionForeignAmount",
props: ['index'],
props: {
index: {},
errors: {},
value: {},
transactionType: {},
sourceCurrencyId: {},
destinationCurrencyId: {},
fractionDigits: {
type: Number,
default: 2
}
},
data() {
return {
currencySymbol: '',
allCurrencies: [],
selectableCurrencies: [],
amount: this.value,
emitEvent: true
}
},
created() {
if ('' !== this.amount) {
this.emitEvent = false;
this.amount = this.formatNumber(this.amount);
}
},
methods: {
formatNumber(str) {
return parseFloat(str).toFixed(this.fractionDigits);
}
},
watch: {
transactionType: function (value) {
// switch (value) {
// case 'Transfer':
// case 'Withdrawal':
// // take currency from source:
// //this.currencyId = this.transactions[this.index].source_account.currency_id;
// this.currencySymbol = this.transactions[this.index].source_account.currency_symbol;
// return;
// case 'Deposit':
// // take currency from destination:
// this.currencyId = this.transactions[this.index].destination_account.currency_id;
// this.currencySymbol = this.transactions[this.index].destination_account.currency_symbol;
// return;
// }
},
destinationAllowedTypes: function (value) {
// // aka source was updated. if source is asset/loan/debt/mortgage use it to set the currency:
// if ('undefined' !== typeof this.transactions[this.index].source_account.type) {
// if (['Asset account', 'Loan', 'Debt', 'Mortgage'].indexOf(this.transactions[this.index].source_account.type) !== -1) {
// // get currency pref from source account
// this.currencyId = this.transactions[this.index].source_account.currency_id;
// this.currencySymbol = this.transactions[this.index].source_account.currency_symbol;
// }
// }
},
sourceAllowedTypes: function (value) {
// // aka destination was updated. if destination is asset/loan/debt/mortgage use it to set the currency:
// // unless its already known to be a transfer
// if ('undefined' !== typeof this.transactions[this.index].destination_account.type && 'Transfer' !== this.transactionType) {
// if (['Asset account', 'Loan', 'Debt', 'Mortgage'].indexOf(this.transactions[this.index].destination_account.type) !== -1) {
// // get currency pref from destination account
// this.currencyId = this.transactions[this.index].destination_account.currency_id;
// this.currencySymbol = this.transactions[this.index].destination_account.currency_symbol;
// }
// }
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;
},
created: function () {
},
methods: {
...mapMutations(
[
'updateField',
],
),
// updateCurrency: function () {
// if (0 === this.currencyId) {
// // use default currency from store.
// this.currencySymbol = this.currencyPreference.symbol;
// this.currencyId = this.currencyPreference.id;
// }
// }
}
},
computed: {
currencyPreference: {
isVisible: {
get() {
return this.$store.state.currencyPreference;
return !('Transfer' === this.transactionType && this.sourceCurrencyId === this.destinationCurrencyId);
}
},
...mapGetters([
'transactionType',
'transactions',
'destinationAllowedTypes',
'sourceAllowedTypes',
]),
amount: {
get() {
return this.transactions[this.index].foreign_amount;
},
set(value) {
this.updateField({field: 'foreign_amount', index: this.index, value: value});
}
},
currencyId: {
get() {
return this.transactions[this.index].foreign_currency_id;
},
set(value) {
this.updateField({field: 'foreign_currency_id', index: this.index, value: value});
}
},
selectedTransactionType: {
get() {
return this.transactionType;
},
set(value) {
// console.log('set selectedAccount for ' + this.direction);
// console.log(value);
// this.updateField({field: this.accountKey, index: this.index, value: value});
}
}
}
}
</script>

View File

@@ -20,10 +20,10 @@
<template>
<!-- FOREIGN Currency -->
<div class="form-group" v-if="selectIsVisible">
<div v-if="isVisible" class="form-group">
<div class="text-xs">&nbsp;</div>
<div class="input-group">
<select name="foreign_currency_id[]" v-model="currencyId" class="form-control">
<select v-model="selectedCurrency" class="form-control" name="foreign_currency_id[]">
<option v-for="currency in selectableCurrencies" :label="currency.name" :value="currency.id">{{ currency.name }}</option>
</select>
</div>
@@ -31,77 +31,53 @@
</template>
<script>
import {createNamespacedHelpers} from "vuex";
const {mapState, mapGetters, mapActions, mapMutations} = createNamespacedHelpers('transactions/create')
export default {
name: "TransactionForeignCurrency",
props: ['index'],
props: [
'index',
'transactionType',
'sourceCurrencyId',
'destinationCurrencyId',
'selectedCurrencyId',
'value'
],
data() {
return {
selectedCurrency: this.value,
allCurrencies: [],
selectableCurrencies: [],
dstCurrencyId: this.destinationCurrencyId,
srcCurrencyId: this.sourceCurrencyId,
lockedCurrency: 0,
selectIsVisible: true
emitEvent: true
}
},
watch: {
value: function (value) {
this.selectedCurrency = value;
},
sourceCurrencyId: function (value) {
this.srcCurrencyId = value;
},
destinationCurrencyId: function (value) {
this.dstCurrencyId = value;
},
selectedCurrency: function (value) {
this.$emit('set-field', {field: 'foreign_currency_id', index: this.index, value: value});
},
transactionType: function (value) {
this.lockedCurrency = 0;
if ('Transfer' === value) {
// take currency from destination:
this.currencyId = this.transactions[this.index].destination_account.currency_id;
this.currencySymbol = this.transactions[this.index].destination_account.currency_symbol;
this.lockedCurrency = this.currencyId;
this.lockedCurrency = this.dstCurrencyId;
this.selectedCurrency = this.dstCurrencyId;
}
this.filterCurrencies();
this.checkVisibility();
},
destinationAllowedTypes: function (value) {
this.lockedCurrency = 0;
if ('Transfer' === this.transactionType) {
// take currency from destination:
this.currencyId = this.transactions[this.index].destination_account.currency_id;
this.currencySymbol = this.transactions[this.index].destination_account.currency_symbol;
this.lockedCurrency = this.currencyId;
}
this.filterCurrencies();
this.checkVisibility();
},
sourceAllowedTypes: function (value) {
this.lockedCurrency = 0;
if ('Transfer' === this.transactionType) {
// take currency from destination:
this.currencyId = this.transactions[this.index].destination_account.currency_id;
this.currencySymbol = this.transactions[this.index].destination_account.currency_symbol;
this.lockedCurrency = this.currencyId;
}
this.filterCurrencies();
this.checkVisibility();
},
},
created: function () {
this.getAllCurrencies();
},
methods: {
...mapMutations(
[
'updateField',
],
),
checkVisibility: function () {
// have the same currency ID, but not zero, and is a transfer
let sourceId = this.transactions[this.index].source_account.currency_id;
let destId = this.transactions[this.index].destination_account.currency_id;
this.selectIsVisible = true;
if (sourceId === destId && 0 !== sourceId && 'Transfer' === this.transactionType) {
this.selectIsVisible = false;
this.currencyId = 0;
}
},
getAllCurrencies: function () {
axios.get('./api/v1/autocomplete/currencies')
.then(response => {
@@ -119,14 +95,13 @@ export default {
let current = this.allCurrencies[key];
if (current.id === this.lockedCurrency) {
this.selectableCurrencies = [current];
this.currencyId = current.id;
this.selectedCurrency = current.id;
}
}
}
return;
}
this.selectableCurrencies = [
{
"id": 0,
@@ -136,62 +111,15 @@ export default {
for (let key in this.allCurrencies) {
if (this.allCurrencies.hasOwnProperty(key) && /^0$|^[1-9]\d*$/.test(key) && key <= 4294967294) {
let current = this.allCurrencies[key];
// add to array if not "locked" in place:
if (this.transactions[this.index].currency_id !== current.id) {
this.selectableCurrencies.push(current);
}
// deselect impossible currency.
if (this.transactions[this.index].currency_id === current.id && this.currencyId === current.id) {
this.currencyId = 0;
}
this.selectableCurrencies.push(current);
}
}
//currency_id
// always add empty currency:
// this.selectableCurrencies = this.allCurrencies;
// this.selectableCurrencies.reverse();
// this.selectableCurrencies.push(
// ;
// this.selectableCurrencies.reverse();
// remove
}
// updateCurrency: function () {
// if (0 === this.currencyId) {
// // use default currency from store.
// this.currencySymbol = this.currencyPreference.symbol;
// this.currencyId = this.currencyPreference.id;
// }
// }
},
computed: {
currencyPreference: {
get() {
return this.$store.state.currencyPreference;
}
},
...mapGetters([
'transactionType',
'transactions',
'destinationAllowedTypes',
'sourceAllowedTypes',
]),
currencyId: {
get() {
return this.transactions[this.index].foreign_currency_id;
},
set(value) {
this.updateField({field: 'foreign_currency_id', index: this.index, value: value});
}
},
normalCurrencyId: {
get() {
return this.transactions[this.index].currency_id;
},
},
isVisible: function () {
return !('Transfer' === this.transactionType && this.srcCurrencyId === this.dstCurrencyId);
}
}
}
</script>

View File

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

View File

@@ -19,47 +19,56 @@
-->
<template>
<div class="form-group">
<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
type="text"
name="internal_reference[]"
v-model="value"
v-model="reference"
:class="errors.length > 0 ? 'form-control is-invalid' : 'form-control'"
:placeholder="$t('firefly.internal_reference')"
class="form-control"/>
name="internal_reference[]"
type="text"
/>
<div class="input-group-append">
<button type="button" class="btn btn-outline-secondary"><i class="far fa-trash-alt"></i></button>
<button class="btn btn-outline-secondary" tabindex="-1" type="button"><i class="far fa-trash-alt"></i></button>
</div>
</div>
</div>
</template>
<script>
import {createNamespacedHelpers} from "vuex";
const {mapState, mapGetters, mapActions, mapMutations} = createNamespacedHelpers('transactions/create')
export default {
props: ['index', 'value'],
props: ['index', 'value', 'errors', 'customFields'],
name: "TransactionInternalReference",
methods: {
...mapMutations(
[
'updateField',
],
),
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.updateField({field: 'internal_reference', index: this.index, value: value});
this.emitEvent = false;
this.reference = value;
},
reference: function (value) {
this.$emit('set-field', {field: 'internal_reference', index: this.index, value: value});
}
}
}
</script>
<style scoped>
</style>

View File

@@ -19,18 +19,18 @@
-->
<template>
<div>
<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="value.length === 0">
<button data-toggle="modal" data-target="#linkModal" class="btn btn-default btn-xs"><i class="fas fa-plus"></i> Add transaction link</button>
<p v-if="links.length === 0">
<button class="btn btn-default btn-xs" data-target="#linkModal" @click="resetModal" data-toggle="modal"><i class="fas fa-plus"></i> Add transaction link</button>
</p>
<ul class="list-group" v-if="value.length > 0">
<li class="list-group-item" v-for="transaction in value">
<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>
@@ -59,24 +59,23 @@
}}</span>)
</span>
<div class="btn-group btn-group-xs float-right">
<a href="#" class="btn btn-xs btn-default"><i class="far fa-edit"></i></a>
<a href="#" class="btn btn-xs btn-danger"><i class="far fa-trash-alt"></i></a>
<button class="btn btn-xs btn-danger" @click="removeLink(index)" tabindex="-1"><i class="far fa-trash-alt"></i></button>
</div>
</li>
</ul>
<div class="form-text" v-if="value.length > 0">
<button data-toggle="modal" data-target="#linkModal" class="btn btn-default"><i class="fas fa-plus"></i></button>
<div v-if="links.length > 0" class="form-text">
<button class="btn btn-default" @click="resetModal" data-target="#linkModal" data-toggle="modal"><i class="fas fa-plus"></i></button>
</div>
</div>
</div>
</div>
<!-- modal -->
<div class="modal" tabindex="-1" id="linkModal">
<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 type="button" class="close" data-dismiss="modal" aria-label="Close">
<button aria-label="Close" class="close" data-dismiss="modal" type="button">
<span aria-hidden="true">&times;</span>
</button>
</div>
@@ -94,10 +93,10 @@
<div class="col">
<form v-on:submit.prevent="search">
<div class="input-group">
<input autocomplete="off" maxlength="255" type="text" name="search" v-model="query" id="query"
class="form-control" placeholder="Search query">
<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 type="submit" class="btn btn-default"><i class="fas fa-search"></i> Search</button>
<button class="btn btn-default" type="submit"><i class="fas fa-search"></i> Search</button>
</div>
</div>
</form>
@@ -107,28 +106,28 @@
<div class="col">
<span v-if="searching"><i class="fas fa-spinner fa-spin"></i></span>
<h4 v-if="searchResults.length > 0">Search results</h4>
<table class="table table-sm" v-if="searchResults.length > 0">
<table v-if="searchResults.length > 0" class="table table-sm">
<thead>
<tr>
<th style="width:33%" colspan="2">Include?</th>
<th colspan="2" style="width:33%">Include?</th>
<th>Transaction</th>
</tr>
</thead>
<tbody>
<tr v-for="result in searchResults">
<td>
<input type="checkbox" class="form-control"
<input v-model="result.selected" class="form-control"
type="checkbox"
@change="selectTransaction($event)"
v-model="result.selected"
/>
</td>
<td>
<select
@change="selectLinkType($event)"
class="form-control"
v-model="result.link_type_id"
class="form-control"
@change="selectLinkType($event)"
>
<option v-for="linkType in linkTypes" :value="linkType.id + '-' + linkType.direction" :label="linkType.type">{{
<option v-for="linkType in linkTypes" :label="linkType.type" :value="linkType.id + '-' + linkType.direction">{{
linkType.type
}}
</option>
@@ -175,7 +174,7 @@
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button class="btn btn-secondary" data-dismiss="modal" type="button">Close</button>
</div>
</div>
</div>
@@ -184,8 +183,10 @@
</template>
<script>
const lodashClonedeep = require('lodash.clonedeep');
// TODO error handling
export default {
props: ['index', 'value'],
props: ['index', 'value', 'errors', 'customFields'],
name: "TransactionLinks",
data() {
return {
@@ -194,27 +195,53 @@ export default {
locale: 'en-US',
linkTypes: [],
query: '',
searching: false
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) {
console.log('Selected transactions is now:');
console.log(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];
console.log(parts);
console.log(current);
if (parts[0] === current.id && parts[1] === current.direction) {
return current.type;
}
@@ -245,27 +272,27 @@ export default {
}
},
updateSelected(journalId, linkTypeId) {
for (let i in this.value) {
if (this.value.hasOwnProperty(i) && /^0$|^[1-9]\d*$/.test(i) && i <= 4294967294) {
let current = this.value[i];
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.value[i].link_type_id = linkTypeId;
this.links[i].link_type_id = linkTypeId;
}
}
}
},
addToSelected(journal) {
const result = this.value.find(({transaction_journal_id}) => transaction_journal_id === journal.transaction_journal_id);
let result = this.links.find(({transaction_journal_id}) => transaction_journal_id === journal.transaction_journal_id);
if (typeof result === 'undefined') {
this.value.push(journal);
this.links.push(journal);
}
},
removeFromSelected(journal) {
for (let i in this.value) {
if (this.value.hasOwnProperty(i) && /^0$|^[1-9]\d*$/.test(i) && i <= 4294967294) {
let current = this.value[i];
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.value.splice(parseInt(i), 1);
this.links.splice(parseInt(i), 1);
}
}
}
@@ -278,6 +305,9 @@ export default {
}
);
},
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) {
@@ -302,6 +332,10 @@ export default {
}
},
search: function () {
if('' === this.query) {
this.searchResults = [];
return;
}
this.searching = true;
this.searchResults = [];
let url = './api/v1/search/transactions?limit=10&query=' + this.query;
@@ -329,9 +363,9 @@ export default {
this.searching = false;
},
getJournalLinkType: function (journalId) {
for (let i in this.value) {
if (this.value.hasOwnProperty(i) && /^0$|^[1-9]\d*$/.test(i) && i <= 4294967294) {
let current = this.value[i];
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;
}
@@ -340,9 +374,9 @@ export default {
return '1-inward';
},
isJournalSelected: function (journalId) {
for (let i in this.value) {
if (this.value.hasOwnProperty(i) && /^0$|^[1-9]\d*$/.test(i) && i <= 4294967294) {
let current = this.value[i];
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;
}

View File

@@ -19,83 +19,91 @@
-->
<template>
<table class="table table-striped table-sm">
<caption style="display:none;">{{ $t('firefly.transaction_table_description') }}</caption>
<thead>
<tr>
<th scope="col" class="text-left">{{ $t('firefly.description') }}</th>
<th scope="col">{{ $t('firefly.opposing_account') }}</th>
<th scope="col" class="text-right">{{ $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>
<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 :href="'accounts/show/' + tr.destination_id" v-if="'withdrawal' === tr.type">{{ tr.destination_name }}</a>
<a :href="'accounts/show/' + tr.source_id" v-if="'deposit' === tr.type">{{ tr.source_name }}</a>
<a :href="'accounts/show/' + tr.destination_id" v-if="'transfer' === tr.type && tr.source_id === account_id">{{ tr.destination_name }}</a>
<a :href="'accounts/show/' + tr.source_id" v-if="'transfer' === tr.type && tr.destination_id === account_id">{{ tr.source_name }}</a>
<br />
<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 && tr.source_id === account_id" :href="'accounts/show/' + tr.destination_id">{{ tr.destination_name }}</a>
<a v-if="'transfer' === tr.type && tr.destination_id === account_id" :href="'accounts/show/' + tr.source_id">{{ tr.source_name }}</a>
<br/>
</span>
</td>
<td style="text-align:right;">
</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('en-US', {style: 'currency', currency: tr.currency_code}).format(tr.amount * -1)}}<br>
{{ 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('en-US', {style: 'currency', currency: tr.currency_code}).format(tr.amount)}}<br>
{{ Intl.NumberFormat(locale, {style: 'currency', currency: tr.currency_code}).format(tr.amount) }}<br>
</span>
<span v-if="'transfer' === tr.type && tr.source_id === account_id" class="text-info">
{{ Intl.NumberFormat('en-US', {style: 'currency', currency: tr.currency_code}).format(tr.amount * -1)}}<br>
{{ Intl.NumberFormat(locale, {style: 'currency', currency: tr.currency_code}).format(tr.amount * -1) }}<br>
</span>
<span v-if="'transfer' === tr.type && tr.destination_id === account_id" class="text-info">
{{ Intl.NumberFormat('en-US', {style: 'currency', currency: tr.currency_code}).format(tr.amount)}}<br>
{{ Intl.NumberFormat(locale, {style: 'currency', currency: tr.currency_code}).format(tr.amount) }}<br>
</span>
</span>
</td>
<td>
</td>
<td>
<span v-for="tr in transaction.attributes.transactions">
<a :href="'categories/show/' + tr.category_id" v-if="0!==tr.category_id">{{ tr.category_name }}</a><br />
<a v-if="0!==tr.category_id" :href="'categories/show/' + tr.category_id">{{ tr.category_name }}</a><br/>
</span>
</td>
<td>
</td>
<td>
<span v-for="tr in transaction.attributes.transactions">
<a :href="'budgets/show/' + tr.budget_id" v-if="0!==tr.budget_id">{{ tr.budget_name }}</a><br />
<a v-if="0!==tr.budget_id" :href="'budgets/show/' + tr.budget_id">{{ tr.budget_name }}</a><br/>
</span>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</template>
<script>
export default {
name: "TransactionListLarge",
props: {
transactions: {
type: Array,
default: function () {
return [];
}
},
account_id: {
type: Number,
default: function () {
return 0;
}
},
}
export default {
name: "TransactionListLarge",
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>
<style scoped>

View File

@@ -19,71 +19,79 @@
-->
<template>
<table class="table table-striped table-sm">
<caption style="display:none;">{{ $t('firefly.transaction_table_description') }}</caption>
<thead>
<tr>
<th scope="col" class="text-left">{{ $t('firefly.description') }}</th>
<th scope="col">{{ $t('firefly.opposing_account') }}</th>
<th scope="col" class="text-right">{{ $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>
<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 :href="'accounts/show/' + tr.destination_id" v-if="'withdrawal' === tr.type">{{ tr.destination_name }}</a>
<a :href="'accounts/show/' + tr.source_id" v-if="'deposit' === tr.type">{{ tr.source_name }}</a>
<a :href="'accounts/show/' + tr.destination_id" v-if="'transfer' === tr.type && tr.source_id === account_id">{{ tr.destination_name }}</a>
<a :href="'accounts/show/' + tr.source_id" v-if="'transfer' === tr.type && tr.destination_id === account_id">{{ tr.source_name }}</a>
<br />
<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 && tr.source_id === account_id" :href="'accounts/show/' + tr.destination_id">{{ tr.destination_name }}</a>
<a v-if="'transfer' === tr.type && tr.destination_id === account_id" :href="'accounts/show/' + tr.source_id">{{ tr.source_name }}</a>
<br/>
</span>
</td>
<td style="text-align:right;">
</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('en-US', {style: 'currency', currency: tr.currency_code}).format(tr.amount * -1)}}<br>
{{ 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('en-US', {style: 'currency', currency: tr.currency_code}).format(tr.amount)}}<br>
{{ Intl.NumberFormat(locale, {style: 'currency', currency: tr.currency_code}).format(tr.amount) }}<br>
</span>
<span v-if="'transfer' === tr.type && tr.source_id === account_id" class="text-info">
{{ Intl.NumberFormat('en-US', {style: 'currency', currency: tr.currency_code}).format(tr.amount * -1)}}<br>
{{ Intl.NumberFormat(locale, {style: 'currency', currency: tr.currency_code}).format(tr.amount * -1) }}<br>
</span>
<span v-if="'transfer' === tr.type && tr.destination_id === account_id" class="text-info">
{{ Intl.NumberFormat('en-US', {style: 'currency', currency: tr.currency_code}).format(tr.amount)}}<br>
{{ Intl.NumberFormat(locale, {style: 'currency', currency: tr.currency_code}).format(tr.amount) }}<br>
</span>
</span>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</template>
<script>
export default {
name: "TransactionListMedium",
props: {
transactions: {
type: Array,
default: function () {
return [];
}
},
account_id: {
type: Number,
default: function () {
return 0;
}
},
}
export default {
name: "TransactionListMedium",
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>
<style scoped>

View File

@@ -23,14 +23,15 @@
<caption style="display:none;">{{ $t('firefly.transaction_table_description') }}</caption>
<thead>
<tr>
<th scope="col" class="text-left">{{ $t('firefly.description') }}</th>
<th scope="col" class="text-right">{{ $t('firefly.amount') }}</th>
<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))">
<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>
@@ -67,8 +68,7 @@ export default {
created() {
this.locale = localStorage.locale ?? 'en-US';
},
methods: {
},
methods: {},
props: {
transactions: {
type: Array,

View File

@@ -0,0 +1,171 @@
<!--
- 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/static/firefly.default_location').then(response => {
this.zoom = parseInt(response.data['firefly.default_location'].zoom_level);
this.center =
[
parseFloat(response.data['firefly.default_location'].latitude),
parseFloat(response.data['firefly.default_location'].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>
<style scoped>
</style>

View File

@@ -20,35 +20,53 @@
<template>
<div class="form-group">
<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="value" class="form-control" :placeholder="$t('firefly.notes')"></textarea>
<textarea
v-model="notes"
:class="errors.length > 0 ? 'form-control is-invalid' : 'form-control'"
:placeholder="$t('firefly.notes')"
></textarea>
</div>
</div>
</template>
<script>
import {createNamespacedHelpers} from "vuex";
const {mapState, mapGetters, mapActions, mapMutations} = createNamespacedHelpers('transactions/create')
export default {
props: ['index', 'value'],
props: ['index', 'value', 'errors', 'customFields'],
name: "TransactionNotes",
methods: {
...mapMutations(
[
'updateField',
],
),
data() {
return {
notes: this.value,
availableFields: this.customFields,
emitEvent: true
}
},
computed: {
showField: function () {
if ('notes' in this.availableFields) {
return this.availableFields.notes;
}
return false;
}
},
watch: {
value: function (value) {
this.updateField({field: 'notes', index: this.index, value: value});
this.emitEvent = false;
this.notes = value;
},
customFields: function (value) {
this.availableFields = value;
},
notes: function (value) {
if (true === this.emitEvent) {
this.$emit('set-field', {field: 'notes', index: this.index, value: value});
}
this.emitEvent = true;
}
}
}

View File

@@ -26,43 +26,39 @@
<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')"
v-model="value"
autocomplete="off"
class="form-control"
name="piggy_bank_id[]"
v-on:submit.prevent
>
<option v-for="piggy in this.piggyList" :value="piggy.id" :label="piggy.name_with_balance">{{ piggy.name_with_balance }}</option>
<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>
import {createNamespacedHelpers} from "vuex";
const {mapState, mapGetters, mapActions, mapMutations} = createNamespacedHelpers('transactions/create')
export default {
props: ['index', 'value'],
props: ['index', 'value', 'errors'],
name: "TransactionPiggyBank",
data() {
return {
piggyList: []
piggyList: [],
piggy_bank_id: this.value,
emitEvent: true
}
},
created() {
this.collectData();
},
methods: {
...mapMutations(
[
'updateField',
],
),
collectData() {
this.piggyList.push(
{
@@ -95,14 +91,15 @@ export default {
},
watch: {
value: function (value) {
this.updateField({field: 'piggy_bank_id', index: this.index, value: value});
this.emitEvent = false;
this.piggy_bank_id = value;
},
piggy_bank_id: function (value) {
if (true === this.emitEvent) {
this.$emit('set-field', {field: 'piggy_bank_id', index: this.index, value: value});
}
this.emitEvent = true;
}
},
computed: {
...mapGetters([
'transactionType',
'transactions',
])
}
}
</script>

View File

@@ -34,61 +34,72 @@
@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 {createNamespacedHelpers} from "vuex";
import VueTagsInput from "@johmun/vue-tags-input";
import axios from "axios";
const {mapState, mapGetters, mapActions, mapMutations} = createNamespacedHelpers('transactions/create')
export default {
name: "TransactionTags",
components: {
VueTagsInput
},
props: ['value', 'index'],
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.
updateTags: true, // the idea is that this is always true, except when the tags-function sets the value.
tagList: this.value,
emitEvent: true
};
},
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) {
console.log('watch: value');
console.log(value);
this.updateField({field: 'tags', index: this.index, value: value});
this.emitEvent = false;
this.tagList = value;
},
tagList: function (value) {
console.log('watch tagList');
if (true === this.emitEvent) {
this.$emit('set-field', {field: 'tags', index: this.index, value: value});
}
this.emitEvent = true;
this.updateTags = false;
this.tags = value;
},
tags: function (value) {
if (this.updateTags) {
console.log('watch: tags');
let shortList = [];
for (let key in value) {
if (value.hasOwnProperty(key)) {
shortList.push({text: value[key].text});
}
}
this.value = shortList;
this.tagList = shortList;
}
this.updateTags = true;
}
},
methods: {
...mapMutations(
[
'updateField',
],
),
initItems() {
if (this.currentTag.length < 2) {
return;

View File

@@ -40,6 +40,7 @@ module.exports = new vuei18n({
'pl': require('./locales/pl.json'),
'fi': require('./locales/fi.json'),
'pt-br': require('./locales/pt-br.json'),
'pt-pt': require('./locales/pt.json'),
'ro': require('./locales/ro.json'),
'ru': require('./locales/ru.json'),
//'zh': require('./locales/zh.json'),

View File

@@ -47,6 +47,8 @@
"go_to_expenses": "\u041e\u0442\u0438\u0434\u0438 \u0432 \u0420\u0430\u0437\u0445\u043e\u0434\u0438",
"go_to_bills": "\u0412\u0438\u0436 \u0441\u043c\u0435\u0442\u043a\u0438\u0442\u0435 \u0441\u0438",
"bills": "\u0421\u043c\u0435\u0442\u043a\u0438",
"last_thirty_days": "\u041f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0442\u0435 \u0442\u0440\u0438\u0439\u0441\u0435\u0442 \u0434\u043d\u0438",
"last_seven_days": "\u041f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0442\u0435 \u0441\u0435\u0434\u0435\u043c \u0434\u043d\u0438",
"go_to_piggies": "\u0412\u0438\u0436 \u043a\u0430\u0441\u0438\u0447\u043a\u0438\u0442\u0435 \u0441\u0438",
"saved": "\u0417\u0430\u043f\u0438\u0441\u0430\u043d",
"piggy_banks": "\u041a\u0430\u0441\u0438\u0447\u043a\u0438",
@@ -56,6 +58,7 @@
"account_role_defaultAsset": "\u0421\u043c\u0435\u0442\u043a\u0430 \u0437\u0430 \u0430\u043a\u0442\u0438\u0432\u0438 \u043f\u043e \u043f\u043e\u0434\u0440\u0430\u0437\u0431\u0438\u0440\u0430\u043d\u0435",
"account_role_savingAsset": "\u0421\u043f\u0435\u0441\u0442\u043e\u0432\u043d\u0430 \u0441\u043c\u0435\u0442\u043a\u0430",
"account_role_sharedAsset": "\u0421\u043c\u0435\u0442\u043a\u0430 \u0437\u0430 \u0441\u043f\u043e\u0434\u0435\u043b\u0435\u043d\u0438 \u0430\u043a\u0442\u0438\u0432\u0438",
"clear_location": "\u0418\u0437\u0447\u0438\u0441\u0442\u0438 \u043c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u0442\u043e",
"account_role_ccAsset": "\u041a\u0440\u0435\u0434\u0438\u0442\u043d\u0430 \u043a\u0430\u0440\u0442\u0430",
"account_role_cashWalletAsset": "\u041f\u0430\u0440\u0438\u0447\u0435\u043d \u043f\u043e\u0440\u0442\u0444\u0435\u0439\u043b",
"daily_budgets": "\u0414\u043d\u0435\u0432\u043d\u0438 \u0431\u044e\u0434\u0436\u0435\u0442\u0438",
@@ -64,6 +67,24 @@
"quarterly_budgets": "\u0422\u0440\u0438\u043c\u0435\u0441\u0435\u0447\u043d\u0438 \u0431\u044e\u0434\u0436\u0435\u0442\u0438",
"half_year_budgets": "\u0428\u0435\u0441\u0442\u043c\u0435\u0441\u0435\u0447\u043d\u0438 \u0431\u044e\u0434\u0436\u0435\u0442\u0438",
"yearly_budgets": "\u0413\u043e\u0434\u0438\u0448\u043d\u0438 \u0431\u044e\u0434\u0436\u0435\u0442\u0438",
"split_transaction_title": "\u041e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u043d\u0430 \u0440\u0430\u0437\u0434\u0435\u043b\u0435\u043d\u0430 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044f",
"errors_submission": "\u0418\u043c\u0430\u0448\u0435 \u043d\u0435\u0449\u043e \u043d\u0435\u0440\u0435\u0434\u043d\u043e \u0441 \u0432\u0430\u0448\u0438\u0442\u0435 \u0434\u0430\u043d\u043d\u0438. \u041c\u043e\u043b\u044f, \u043f\u0440\u043e\u0432\u0435\u0440\u0435\u0442\u0435 \u0433\u0440\u0435\u0448\u043a\u0438\u0442\u0435.",
"flash_error": "\u0413\u0440\u0435\u0448\u043a\u0430!",
"store_transaction": "Store transaction",
"flash_success": "\u0423\u0441\u043f\u0435\u0445!",
"create_another": "\u0421\u043b\u0435\u0434 \u0441\u044a\u0445\u0440\u0430\u043d\u044f\u0432\u0430\u043d\u0435\u0442\u043e \u0441\u0435 \u0432\u044a\u0440\u043d\u0435\u0442\u0435 \u0442\u0443\u043a, \u0437\u0430 \u0434\u0430 \u0441\u044a\u0437\u0434\u0430\u0434\u0435\u0442\u0435 \u043d\u043e\u0432\u0430.",
"update_transaction": "\u041e\u0431\u043d\u043e\u0432\u0438 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044f\u0442\u0430",
"after_update_create_another": "\u0421\u043b\u0435\u0434 \u043e\u0431\u043d\u043e\u0432\u044f\u0432\u0430\u043d\u0435\u0442\u043e \u0441\u0435 \u0432\u044a\u0440\u043d\u0435\u0442\u0435 \u0442\u0443\u043a, \u0437\u0430 \u0434\u0430 \u043f\u0440\u043e\u0434\u044a\u043b\u0436\u0438\u0442\u0435 \u0441 \u0440\u0435\u0434\u0430\u043a\u0446\u0438\u044f\u0442\u0430.",
"reset_after": "\u0418\u0437\u0447\u0438\u0441\u0442\u0432\u0430\u043d\u0435 \u043d\u0430 \u0444\u043e\u0440\u043c\u0443\u043b\u044f\u0440\u0430 \u0441\u043b\u0435\u0434 \u0438\u0437\u043f\u0440\u0430\u0449\u0430\u043d\u0435",
"bill_paid_on": "Paid on {date}",
"first_split_decides": "The first split determines the value of this field",
"first_split_overrules_source": "The first split may overrule the source account",
"first_split_overrules_destination": "The first split may overrule the destination account",
"transaction_stored_link": "<a href=\"transactions\/show\/{ID}\">\u0422\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044f #{ID}(\"{title}\")<\/a> \u0431\u0435\u0448\u0435 \u0437\u0430\u043f\u0438\u0441\u0430\u043d\u0430.",
"custom_period": "Custom period",
"reset_to_current": "Reset to current period",
"select_period": "Select a period",
"location": "\u041c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435",
"other_budgets": "\u0412\u0440\u0435\u043c\u0435\u0432\u043e \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u043b\u0438\u0437\u0438\u0440\u0430\u043d\u0438 \u0431\u044e\u0434\u0436\u0435\u0442\u0438",
"journal_links": "\u0412\u0440\u044a\u0437\u043a\u0438 \u043d\u0430 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044f",
"go_to_withdrawals": "\u0412\u0438\u0436\u0442\u0435 \u0442\u0435\u0433\u043b\u0435\u043d\u0438\u044f\u0442\u0430 \u0441\u0438",

View File

@@ -47,6 +47,8 @@
"go_to_expenses": "Go to expenses",
"go_to_bills": "P\u0159ej\u00edt k \u00fa\u010dt\u016fm",
"bills": "\u00da\u010dty",
"last_thirty_days": "Uplynul\u00fdch 30 dn\u00ed",
"last_seven_days": "Uplynul\u00fdch 7 dn\u016f",
"go_to_piggies": "P\u0159ej\u00edt k pokladni\u010dk\u00e1m",
"saved": "Saved",
"piggy_banks": "Pokladni\u010dky",
@@ -56,6 +58,7 @@
"account_role_defaultAsset": "V\u00fdchoz\u00ed \u00fa\u010det aktiv",
"account_role_savingAsset": "Spo\u0159ic\u00ed \u00fa\u010det",
"account_role_sharedAsset": "Sd\u00edlen\u00fd \u00fa\u010det aktiv",
"clear_location": "Vymazat um\u00edst\u011bn\u00ed",
"account_role_ccAsset": "Kreditn\u00ed karta",
"account_role_cashWalletAsset": "Pen\u011b\u017eenka",
"daily_budgets": "Daily budgets",
@@ -64,6 +67,24 @@
"quarterly_budgets": "Quarterly budgets",
"half_year_budgets": "Half-yearly budgets",
"yearly_budgets": "Yearly budgets",
"split_transaction_title": "Popis roz\u00fa\u010dtov\u00e1n\u00ed",
"errors_submission": "There was something wrong with your submission. Please check out the errors.",
"flash_error": "Chyba!",
"store_transaction": "Store transaction",
"flash_success": "\u00dasp\u011b\u0161n\u011b dokon\u010deno!",
"create_another": "After storing, return here to create another one.",
"update_transaction": "Update transaction",
"after_update_create_another": "After updating, return here to continue editing.",
"reset_after": "Reset form after submission",
"bill_paid_on": "Paid on {date}",
"first_split_decides": "The first split determines the value of this field",
"first_split_overrules_source": "The first split may overrule the source account",
"first_split_overrules_destination": "The first split may overrule the destination account",
"transaction_stored_link": "<a href=\"transactions\/show\/{ID}\">Transaction #{ID} (\"{title}\")<\/a> has been stored.",
"custom_period": "Custom period",
"reset_to_current": "Reset to current period",
"select_period": "Select a period",
"location": "Um\u00edst\u011bn\u00ed",
"other_budgets": "Custom timed budgets",
"journal_links": "Transaction links",
"go_to_withdrawals": "Go to your withdrawals",

View File

@@ -47,6 +47,8 @@
"go_to_expenses": "Zu Ausgaben wechseln",
"go_to_bills": "Rechnungen anzeigen",
"bills": "Rechnungen",
"last_thirty_days": "Letzte 30 Tage",
"last_seven_days": "Letzte sieben Tage",
"go_to_piggies": "Sparschweine anzeigen",
"saved": "Gespeichert",
"piggy_banks": "Sparschweine",
@@ -56,6 +58,7 @@
"account_role_defaultAsset": "Standard-Bestandskonto",
"account_role_savingAsset": "Sparkonto",
"account_role_sharedAsset": "Gemeinsames Bestandskonto",
"clear_location": "Ort leeren",
"account_role_ccAsset": "Kreditkarte",
"account_role_cashWalletAsset": "Geldb\u00f6rse",
"daily_budgets": "Tagesbudgets",
@@ -64,6 +67,24 @@
"quarterly_budgets": "Quartalsbudgets",
"half_year_budgets": "Halbjahresbudgets",
"yearly_budgets": "Jahresbudgets",
"split_transaction_title": "Beschreibung der Splittbuchung",
"errors_submission": "Ihre \u00dcbermittlung ist fehlgeschlagen. Bitte \u00fcberpr\u00fcfen Sie die Fehler.",
"flash_error": "Fehler!",
"store_transaction": "Buchung speichern",
"flash_success": "Geschafft!",
"create_another": "Nach dem Speichern hierher zur\u00fcckkehren, um ein weiteres zu erstellen.",
"update_transaction": "Buchung aktualisieren",
"after_update_create_another": "Nach dem Aktualisieren hierher zur\u00fcckkehren, um weiter zu bearbeiten.",
"reset_after": "Formular nach der \u00dcbermittlung zur\u00fccksetzen",
"bill_paid_on": "Bezahlt am {date}",
"first_split_decides": "Die erste Aufteilung bestimmt den Wert dieses Feldes",
"first_split_overrules_source": "Die erste Aufteilung k\u00f6nnte das Quellkonto \u00fcberschreiben",
"first_split_overrules_destination": "Die erste Aufteilung k\u00f6nnte das Zielkonto \u00fcberschreiben",
"transaction_stored_link": "<a href=\"transactions\/show\/{ID}\">Buchung #{ID} (\"{title}\")<\/a> wurde gespeichert.",
"custom_period": "Benutzerdefinierter Zeitraum",
"reset_to_current": "Auf aktuellen Zeitraum zur\u00fccksetzen",
"select_period": "Zeitraum ausw\u00e4hlen",
"location": "Standort",
"other_budgets": "Zeitlich befristete Budgets",
"journal_links": "Buchungsverkn\u00fcpfungen",
"go_to_withdrawals": "Ausgaben anzeigen",

View File

@@ -47,6 +47,8 @@
"go_to_expenses": "\u03a0\u03b7\u03b3\u03b1\u03af\u03bd\u03b5\u03c4\u03b5 \u03c3\u03c4\u03b9\u03c2 \u03b4\u03b1\u03c0\u03ac\u03bd\u03b5\u03c2",
"go_to_bills": "\u03a0\u03b7\u03b3\u03b1\u03af\u03bd\u03b5\u03c4\u03b5 \u03c3\u03c4\u03b1 \u03c0\u03ac\u03b3\u03b9\u03b1 \u03ad\u03be\u03bf\u03b4\u03b1",
"bills": "\u03a0\u03ac\u03b3\u03b9\u03b1 \u03ad\u03be\u03bf\u03b4\u03b1",
"last_thirty_days": "\u03a4\u03b5\u03bb\u03b5\u03c5\u03c4\u03b1\u03af\u03b5\u03c2 \u03c4\u03c1\u03b9\u03ac\u03bd\u03c4\u03b1 \u03b7\u03bc\u03ad\u03c1\u03b5\u03c2",
"last_seven_days": "\u03a4\u03b5\u03bb\u03b5\u03c5\u03c4\u03b1\u03af\u03b5\u03c2 \u03b5\u03c0\u03c4\u03ac \u03b7\u03bc\u03ad\u03c1\u03b5\u03c2",
"go_to_piggies": "\u03a0\u03b7\u03b3\u03b1\u03af\u03bd\u03b5\u03c4\u03b5 \u03c3\u03c4\u03bf\u03c5\u03c2 \u03ba\u03bf\u03c5\u03bc\u03c0\u03b1\u03c1\u03ac\u03b4\u03b5\u03c2 \u03c3\u03b1\u03c2",
"saved": "\u0391\u03c0\u03bf\u03b8\u03b7\u03ba\u03b5\u03cd\u03c4\u03b7\u03ba\u03b5",
"piggy_banks": "\u039a\u03bf\u03c5\u03bc\u03c0\u03b1\u03c1\u03ac\u03b4\u03b5\u03c2",
@@ -56,6 +58,7 @@
"account_role_defaultAsset": "\u0392\u03b1\u03c3\u03b9\u03ba\u03cc\u03c2 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ba\u03b5\u03c6\u03b1\u03bb\u03b1\u03af\u03bf\u03c5",
"account_role_savingAsset": "\u039b\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03b1\u03c0\u03bf\u03c4\u03b1\u03bc\u03af\u03b5\u03c5\u03c3\u03b7\u03c2",
"account_role_sharedAsset": "\u039a\u03bf\u03b9\u03bd\u03cc\u03c2 \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03ba\u03b5\u03c6\u03b1\u03bb\u03b1\u03af\u03bf\u03c5",
"clear_location": "\u0395\u03ba\u03ba\u03b1\u03b8\u03ac\u03c1\u03b9\u03c3\u03b7 \u03c4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1\u03c2",
"account_role_ccAsset": "\u03a0\u03b9\u03c3\u03c4\u03c9\u03c4\u03b9\u03ba\u03ae \u03ba\u03ac\u03c1\u03c4\u03b1",
"account_role_cashWalletAsset": "\u03a0\u03bf\u03c1\u03c4\u03bf\u03c6\u03cc\u03bb\u03b9 \u03bc\u03b5\u03c4\u03c1\u03b7\u03c4\u03ce\u03bd",
"daily_budgets": "\u0397\u03bc\u03b5\u03c1\u03ae\u03c3\u03b9\u03bf\u03b9 \u03c0\u03c1\u03bf\u03cb\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03bc\u03bf\u03af",
@@ -64,6 +67,24 @@
"quarterly_budgets": "\u03a4\u03c1\u03b9\u03bc\u03b7\u03bd\u03b9\u03b1\u03af\u03bf\u03b9 \u03c0\u03c1\u03bf\u03cb\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03bc\u03bf\u03af",
"half_year_budgets": "\u0395\u03be\u03b1\u03bc\u03b7\u03bd\u03b9\u03b1\u03af\u03bf\u03b9 \u03c0\u03c1\u03bf\u03cb\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03bc\u03bf\u03af",
"yearly_budgets": "\u0395\u03c4\u03ae\u03c3\u03b9\u03bf\u03b9 \u03c0\u03c1\u03bf\u03cb\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03bc\u03bf\u03af",
"split_transaction_title": "\u03a0\u03b5\u03c1\u03b9\u03b3\u03c1\u03b1\u03c6\u03ae \u03c4\u03b7\u03c2 \u03c3\u03c5\u03bd\u03b1\u03bb\u03bb\u03b1\u03b3\u03ae\u03c2 \u03bc\u03b5 \u03b4\u03b9\u03b1\u03c7\u03c9\u03c1\u03b9\u03c3\u03bc\u03cc",
"errors_submission": "\u03a5\u03c0\u03ae\u03c1\u03be\u03b5 \u03ba\u03ac\u03c0\u03bf\u03b9\u03bf \u03bb\u03ac\u03b8\u03bf\u03c2 \u03bc\u03b5 \u03c4\u03b7\u03bd \u03c5\u03c0\u03bf\u03b2\u03bf\u03bb\u03ae \u03c3\u03b1\u03c2. \u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03ce \u03b5\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03b1 \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1\u03c4\u03b1.",
"flash_error": "\u03a3\u03c6\u03ac\u03bb\u03bc\u03b1!",
"store_transaction": "Store transaction",
"flash_success": "\u0395\u03c0\u03b9\u03c4\u03c5\u03c7\u03af\u03b1!",
"create_another": "\u039c\u03b5\u03c4\u03ac \u03c4\u03b7\u03bd \u03b1\u03c0\u03bf\u03b8\u03ae\u03ba\u03b5\u03c5\u03c3\u03b7, \u03b5\u03c0\u03b9\u03c3\u03c4\u03c1\u03ad\u03c8\u03c4\u03b5 \u03b5\u03b4\u03ce \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b4\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03ae\u03c3\u03b5\u03c4\u03b5 \u03b1\u03ba\u03cc\u03bc\u03b7 \u03ad\u03bd\u03b1.",
"update_transaction": "\u0395\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7 \u03c3\u03c5\u03bd\u03b1\u03bb\u03bb\u03b1\u03b3\u03ae\u03c2",
"after_update_create_another": "\u039c\u03b5\u03c4\u03ac \u03c4\u03b7\u03bd \u03b5\u03bd\u03b7\u03bc\u03ad\u03c1\u03c9\u03c3\u03b7, \u03b5\u03c0\u03b9\u03c3\u03c4\u03c1\u03ad\u03c8\u03c4\u03b5 \u03b5\u03b4\u03ce \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b5\u03c7\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03c0\u03b5\u03be\u03b5\u03c1\u03b3\u03b1\u03c3\u03af\u03b1.",
"reset_after": "\u0395\u03c0\u03b1\u03bd\u03b1\u03c6\u03bf\u03c1\u03ac \u03c6\u03cc\u03c1\u03bc\u03b1\u03c2 \u03bc\u03b5\u03c4\u03ac \u03c4\u03b7\u03bd \u03c5\u03c0\u03bf\u03b2\u03bf\u03bb\u03ae",
"bill_paid_on": "Paid on {date}",
"first_split_decides": "The first split determines the value of this field",
"first_split_overrules_source": "The first split may overrule the source account",
"first_split_overrules_destination": "The first split may overrule the destination account",
"transaction_stored_link": "<a href=\"transactions\/show\/{ID}\">\u0397 \u03c3\u03c5\u03bd\u03b1\u03bb\u03bb\u03b1\u03b3\u03ae #{ID} (\"{title}\")<\/a> \u03ad\u03c7\u03b5\u03b9 \u03b1\u03c0\u03bf\u03b8\u03b7\u03ba\u03b5\u03c5\u03c4\u03b5\u03af.",
"custom_period": "Custom period",
"reset_to_current": "Reset to current period",
"select_period": "Select a period",
"location": "\u03a4\u03bf\u03c0\u03bf\u03b8\u03b5\u03c3\u03af\u03b1",
"other_budgets": "\u03a0\u03c1\u03bf\u03cb\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03bc\u03bf\u03af \u03bc\u03b5 \u03c7\u03c1\u03bf\u03bd\u03b9\u03ba\u03ae \u03c0\u03c1\u03bf\u03c3\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae",
"journal_links": "\u03a3\u03c5\u03bd\u03b4\u03ad\u03c3\u03b5\u03b9\u03c2 \u03c3\u03c5\u03bd\u03b1\u03bb\u03bb\u03b1\u03b3\u03ce\u03bd",
"go_to_withdrawals": "\u03a0\u03b7\u03b3\u03b1\u03af\u03bd\u03b5\u03c4\u03b5 \u03c3\u03c4\u03b9\u03c2 \u03b1\u03bd\u03b1\u03bb\u03ae\u03c8\u03b5\u03b9\u03c2 \u03c3\u03b1\u03c2",

View File

@@ -47,6 +47,8 @@
"go_to_expenses": "Go to expenses",
"go_to_bills": "Go to your bills",
"bills": "Bills",
"last_thirty_days": "Last thirty days",
"last_seven_days": "Last seven days",
"go_to_piggies": "Go to your piggy banks",
"saved": "Saved",
"piggy_banks": "Piggy banks",
@@ -56,6 +58,7 @@
"account_role_defaultAsset": "Default asset account",
"account_role_savingAsset": "Savings account",
"account_role_sharedAsset": "Shared asset account",
"clear_location": "Clear location",
"account_role_ccAsset": "Credit card",
"account_role_cashWalletAsset": "Cash wallet",
"daily_budgets": "Daily budgets",
@@ -64,6 +67,24 @@
"quarterly_budgets": "Quarterly budgets",
"half_year_budgets": "Half-yearly budgets",
"yearly_budgets": "Yearly budgets",
"split_transaction_title": "Description of the split transaction",
"errors_submission": "There was something wrong with your submission. Please check out the errors.",
"flash_error": "Error!",
"store_transaction": "Store transaction",
"flash_success": "Success!",
"create_another": "After storing, return here to create another one.",
"update_transaction": "Update transaction",
"after_update_create_another": "After updating, return here to continue editing.",
"reset_after": "Reset form after submission",
"bill_paid_on": "Paid on {date}",
"first_split_decides": "The first split determines the value of this field",
"first_split_overrules_source": "The first split may overrule the source account",
"first_split_overrules_destination": "The first split may overrule the destination account",
"transaction_stored_link": "<a href=\"transactions\/show\/{ID}\">Transaction #{ID} (\"{title}\")<\/a> has been stored.",
"custom_period": "Custom period",
"reset_to_current": "Reset to current period",
"select_period": "Select a period",
"location": "Location",
"other_budgets": "Custom timed budgets",
"journal_links": "Transaction links",
"go_to_withdrawals": "Go to your withdrawals",

View File

@@ -47,6 +47,8 @@
"go_to_expenses": "Go to expenses",
"go_to_bills": "Go to your bills",
"bills": "Bills",
"last_thirty_days": "Last thirty days",
"last_seven_days": "Last seven days",
"go_to_piggies": "Go to your piggy banks",
"saved": "Saved",
"piggy_banks": "Piggy banks",
@@ -56,6 +58,7 @@
"account_role_defaultAsset": "Default asset account",
"account_role_savingAsset": "Savings account",
"account_role_sharedAsset": "Shared asset account",
"clear_location": "Clear location",
"account_role_ccAsset": "Credit card",
"account_role_cashWalletAsset": "Cash wallet",
"daily_budgets": "Daily budgets",
@@ -64,6 +67,24 @@
"quarterly_budgets": "Quarterly budgets",
"half_year_budgets": "Half-yearly budgets",
"yearly_budgets": "Yearly budgets",
"split_transaction_title": "Description of the split transaction",
"errors_submission": "There was something wrong with your submission. Please check out the errors.",
"flash_error": "Error!",
"store_transaction": "Store transaction",
"flash_success": "Success!",
"create_another": "After storing, return here to create another one.",
"update_transaction": "Update transaction",
"after_update_create_another": "After updating, return here to continue editing.",
"reset_after": "Reset form after submission",
"bill_paid_on": "Paid on {date}",
"first_split_decides": "The first split determines the value of this field",
"first_split_overrules_source": "The first split may overrule the source account",
"first_split_overrules_destination": "The first split may overrule the destination account",
"transaction_stored_link": "<a href=\"transactions\/show\/{ID}\">Transaction #{ID} (\"{title}\")<\/a> has been stored.",
"custom_period": "Custom period",
"reset_to_current": "Reset to current period",
"select_period": "Select a period",
"location": "Location",
"other_budgets": "Custom timed budgets",
"journal_links": "Transaction links",
"go_to_withdrawals": "Go to your withdrawals",

View File

@@ -47,6 +47,8 @@
"go_to_expenses": "Ir a gastos",
"go_to_bills": "Ir a tus cuentas",
"bills": "Facturas",
"last_thirty_days": "\u00daltimos treinta d\u00edas",
"last_seven_days": "\u00daltimos siete d\u00edas",
"go_to_piggies": "Ir a tu hucha",
"saved": "Guardado",
"piggy_banks": "Huchas",
@@ -56,6 +58,7 @@
"account_role_defaultAsset": "Cuentas de ingresos por defecto",
"account_role_savingAsset": "Cuentas de ahorros",
"account_role_sharedAsset": "Cuenta de ingresos compartida",
"clear_location": "Eliminar ubicaci\u00f3n",
"account_role_ccAsset": "Tarjeta de Cr\u00e9dito",
"account_role_cashWalletAsset": "Billetera de efectivo",
"daily_budgets": "Presupuestos diarios",
@@ -64,6 +67,24 @@
"quarterly_budgets": "Presupuestos trimestrales",
"half_year_budgets": "Presupuestos semestrales",
"yearly_budgets": "Presupuestos anuales",
"split_transaction_title": "Descripci\u00f3n de la transacci\u00f3n dividida",
"errors_submission": "Hubo un problema con su env\u00edo. Por favor, compruebe los errores.",
"flash_error": "\u00a1Error!",
"store_transaction": "Guardar transacci\u00f3n",
"flash_success": "\u00a1Operaci\u00f3n correcta!",
"create_another": "Despu\u00e9s de guardar, vuelve aqu\u00ed para crear otro.",
"update_transaction": "Actualizar transacci\u00f3n",
"after_update_create_another": "Despu\u00e9s de actualizar, vuelve aqu\u00ed para continuar editando.",
"reset_after": "Restablecer formulario despu\u00e9s del env\u00edo",
"bill_paid_on": "Pagado el {date}",
"first_split_decides": "The first split determines the value of this field",
"first_split_overrules_source": "The first split may overrule the source account",
"first_split_overrules_destination": "The first split may overrule the destination account",
"transaction_stored_link": "<a href=\"transactions\/show\/{ID}\">La transacci\u00f3n #{ID} (\"{title}\")<\/a> ha sido almacenada.",
"custom_period": "Custom period",
"reset_to_current": "Reset to current period",
"select_period": "Select a period",
"location": "Ubicaci\u00f3n",
"other_budgets": "Presupuestos de tiempo personalizado",
"journal_links": "Enlaces de transacciones",
"go_to_withdrawals": "Ir a tus retiradas",

View File

@@ -47,6 +47,8 @@
"go_to_expenses": "Go to expenses",
"go_to_bills": "Avaa omat laskut",
"bills": "Laskut",
"last_thirty_days": "Viimeiset 30 p\u00e4iv\u00e4\u00e4",
"last_seven_days": "Viimeiset 7 p\u00e4iv\u00e4\u00e4",
"go_to_piggies": "Tarkastele s\u00e4\u00e4st\u00f6possujasi",
"saved": "Saved",
"piggy_banks": "S\u00e4\u00e4st\u00f6possut",
@@ -56,6 +58,7 @@
"account_role_defaultAsset": "Oletusk\u00e4ytt\u00f6tili",
"account_role_savingAsset": "S\u00e4\u00e4st\u00f6tili",
"account_role_sharedAsset": "Jaettu k\u00e4ytt\u00f6tili",
"clear_location": "Tyhjenn\u00e4 sijainti",
"account_role_ccAsset": "Luottokortti",
"account_role_cashWalletAsset": "K\u00e4teinen",
"daily_budgets": "Daily budgets",
@@ -64,6 +67,24 @@
"quarterly_budgets": "Quarterly budgets",
"half_year_budgets": "Half-yearly budgets",
"yearly_budgets": "Yearly budgets",
"split_transaction_title": "Jaetun tapahtuman kuvaus",
"errors_submission": "There was something wrong with your submission. Please check out the errors.",
"flash_error": "Virhe!",
"store_transaction": "Store transaction",
"flash_success": "Valmista tuli!",
"create_another": "Tallennuksen j\u00e4lkeen, palaa takaisin luomaan uusi tapahtuma.",
"update_transaction": "P\u00e4ivit\u00e4 tapahtuma",
"after_update_create_another": "P\u00e4ivityksen j\u00e4lkeen, palaa takaisin jatkamaan muokkausta.",
"reset_after": "Tyhjenn\u00e4 lomake l\u00e4hetyksen j\u00e4lkeen",
"bill_paid_on": "Paid on {date}",
"first_split_decides": "The first split determines the value of this field",
"first_split_overrules_source": "The first split may overrule the source account",
"first_split_overrules_destination": "The first split may overrule the destination account",
"transaction_stored_link": "<a href=\"transactions\/show\/{ID}\">Transaction #{ID} (\"{title}\")<\/a> has been stored.",
"custom_period": "Custom period",
"reset_to_current": "Reset to current period",
"select_period": "Select a period",
"location": "Sijainti",
"other_budgets": "Custom timed budgets",
"journal_links": "Tapahtuman linkit",
"go_to_withdrawals": "Go to your withdrawals",

View File

@@ -47,6 +47,8 @@
"go_to_expenses": "Aller aux d\u00e9penses",
"go_to_bills": "G\u00e9rer vos factures",
"bills": "Factures",
"last_thirty_days": "Trente derniers jours",
"last_seven_days": "7 Derniers Jours",
"go_to_piggies": "G\u00e9rer vos tirelires",
"saved": "Sauvegard\u00e9",
"piggy_banks": "Tirelires",
@@ -56,6 +58,7 @@
"account_role_defaultAsset": "Compte d'actif par d\u00e9faut",
"account_role_savingAsset": "Compte d\u2019\u00e9pargne",
"account_role_sharedAsset": "Compte d'actif partag\u00e9",
"clear_location": "Effacer la localisation",
"account_role_ccAsset": "Carte de cr\u00e9dit",
"account_role_cashWalletAsset": "Porte-monnaie",
"daily_budgets": "Budgets quotidiens",
@@ -64,6 +67,24 @@
"quarterly_budgets": "Budgets trimestriels",
"half_year_budgets": "Budgets semestriels",
"yearly_budgets": "Budgets annuels",
"split_transaction_title": "Description de l'op\u00e9ration ventil\u00e9e",
"errors_submission": "Certaines informations ne sont pas correctes dans votre formulaire. Veuillez v\u00e9rifier les erreurs.",
"flash_error": "Erreur !",
"store_transaction": "Enregistrer l'op\u00e9ration",
"flash_success": "Super !",
"create_another": "Apr\u00e8s enregistrement, revenir ici pour en cr\u00e9er un nouveau.",
"update_transaction": "Mettre \u00e0 jour l'op\u00e9ration",
"after_update_create_another": "Apr\u00e8s la mise \u00e0 jour, revenir ici pour continuer l'\u00e9dition.",
"reset_after": "R\u00e9initialiser le formulaire apr\u00e8s soumission",
"bill_paid_on": "Pay\u00e9 le {date}",
"first_split_decides": "La premi\u00e8re ventilation d\u00e9termine la valeur de ce champ",
"first_split_overrules_source": "La premi\u00e8re ventilation peut remplacer le compte source",
"first_split_overrules_destination": "La premi\u00e8re ventilation peut remplacer le compte de destination",
"transaction_stored_link": "<a href=\"transactions\/show\/{ID}\">L'op\u00e9ration n\u00b0{ID} (\"{title}\")<\/a> a \u00e9t\u00e9 enregistr\u00e9e.",
"custom_period": "P\u00e9riode personnalis\u00e9e",
"reset_to_current": "R\u00e9initialiser \u00e0 la p\u00e9riode en cours",
"select_period": "S\u00e9lectionnez une p\u00e9riode",
"location": "Emplacement",
"other_budgets": "Budgets \u00e0 p\u00e9riode personnalis\u00e9e",
"journal_links": "Liens d'op\u00e9ration",
"go_to_withdrawals": "Acc\u00e9der \u00e0 vos retraits",

View File

@@ -47,6 +47,8 @@
"go_to_expenses": "Ugr\u00e1s a kiad\u00e1sokra",
"go_to_bills": "Ugr\u00e1s a sz\u00e1ml\u00e1khoz",
"bills": "Sz\u00e1ml\u00e1k",
"last_thirty_days": "Elm\u00falt harminc nap",
"last_seven_days": "Utols\u00f3 h\u00e9t nap",
"go_to_piggies": "Ugr\u00e1s a malacperselyekhez",
"saved": "Mentve",
"piggy_banks": "Malacperselyek",
@@ -56,6 +58,7 @@
"account_role_defaultAsset": "Alap\u00e9rtelmezett eszk\u00f6zsz\u00e1mla",
"account_role_savingAsset": "Megtakar\u00edt\u00e1si sz\u00e1mla",
"account_role_sharedAsset": "Megosztott eszk\u00f6zsz\u00e1mla",
"clear_location": "Hely t\u00f6rl\u00e9se",
"account_role_ccAsset": "Hitelk\u00e1rtya",
"account_role_cashWalletAsset": "K\u00e9szp\u00e9nz",
"daily_budgets": "Daily budgets",
@@ -64,6 +67,24 @@
"quarterly_budgets": "Quarterly budgets",
"half_year_budgets": "Half-yearly budgets",
"yearly_budgets": "Yearly budgets",
"split_transaction_title": "Felosztott tranzakci\u00f3 le\u00edr\u00e1sa",
"errors_submission": "There was something wrong with your submission. Please check out the errors.",
"flash_error": "Hiba!",
"store_transaction": "Store transaction",
"flash_success": "Siker!",
"create_another": "A t\u00e1rol\u00e1s ut\u00e1n t\u00e9rjen vissza ide \u00faj l\u00e9trehoz\u00e1s\u00e1hoz.",
"update_transaction": "Tranzakci\u00f3 friss\u00edt\u00e9se",
"after_update_create_another": "A friss\u00edt\u00e9s ut\u00e1n t\u00e9rjen vissza ide a szerkeszt\u00e9s folytat\u00e1s\u00e1hoz.",
"reset_after": "\u0170rlap t\u00f6rl\u00e9se a bek\u00fcld\u00e9s ut\u00e1n",
"bill_paid_on": "Paid on {date}",
"first_split_decides": "The first split determines the value of this field",
"first_split_overrules_source": "The first split may overrule the source account",
"first_split_overrules_destination": "The first split may overrule the destination account",
"transaction_stored_link": "<a href=\"transactions\/show\/{ID}\">Transaction #{ID} (\"{title}\")<\/a> mentve.",
"custom_period": "Custom period",
"reset_to_current": "Reset to current period",
"select_period": "Select a period",
"location": "Hely",
"other_budgets": "Custom timed budgets",
"journal_links": "Tranzakci\u00f3 \u00f6sszekapcsol\u00e1sok",
"go_to_withdrawals": "Ugr\u00e1s a k\u00f6lts\u00e9gekhez",

View File

@@ -47,6 +47,8 @@
"go_to_expenses": "Vai alle spese",
"go_to_bills": "Vai alle tue bollette",
"bills": "Bollette",
"last_thirty_days": "Ultimi trenta giorni",
"last_seven_days": "Ultimi sette giorni",
"go_to_piggies": "Vai ai tuoi salvadanai",
"saved": "Salvata",
"piggy_banks": "Salvadanai",
@@ -56,6 +58,7 @@
"account_role_defaultAsset": "Conto attivit\u00e0 predefinito",
"account_role_savingAsset": "Conto risparmio",
"account_role_sharedAsset": "Conto attivit\u00e0 condiviso",
"clear_location": "Rimuovi dalla posizione",
"account_role_ccAsset": "Carta di credito",
"account_role_cashWalletAsset": "Portafoglio",
"daily_budgets": "Budget giornalieri",
@@ -64,6 +67,24 @@
"quarterly_budgets": "Bilanci trimestrali",
"half_year_budgets": "Bilanci semestrali",
"yearly_budgets": "Budget annuali",
"split_transaction_title": "Descrizione della transazione suddivisa",
"errors_submission": "Errore durante l'invio. Controlla gli errori segnalati qui sotto.",
"flash_error": "Errore!",
"store_transaction": "Salva transazione",
"flash_success": "Successo!",
"create_another": "Dopo il salvataggio, torna qui per crearne un'altra.",
"update_transaction": "Aggiorna transazione",
"after_update_create_another": "Dopo l'aggiornamento, torna qui per continuare la modifica.",
"reset_after": "Resetta il modulo dopo l'invio",
"bill_paid_on": "Pagata il {date}",
"first_split_decides": "La prima suddivisione determina il valore di questo campo",
"first_split_overrules_source": "La prima suddivisione potrebbe sovrascrivere l'account di origine",
"first_split_overrules_destination": "La prima suddivisione potrebbe sovrascrivere l'account di destinazione",
"transaction_stored_link": "La <a href=\"transactions\/show\/{ID}\">transazione #{ID} (\"{title}\")<\/a> \u00e8 stata salvata.",
"custom_period": "Periodo personalizzato",
"reset_to_current": "Ripristina il periodo corrente",
"select_period": "Seleziona il periodo",
"location": "Posizione",
"other_budgets": "Budget a periodi personalizzati",
"journal_links": "Collegamenti della transazione",
"go_to_withdrawals": "Vai ai tuoi prelievi",

View File

@@ -47,6 +47,8 @@
"go_to_expenses": "Go to expenses",
"go_to_bills": "G\u00e5 til regningene dine",
"bills": "Regninger",
"last_thirty_days": "Tredve siste dager",
"last_seven_days": "Syv siste dager",
"go_to_piggies": "G\u00e5 til sparegrisene dine",
"saved": "Saved",
"piggy_banks": "Sparegriser",
@@ -56,6 +58,7 @@
"account_role_defaultAsset": "Standard aktivakonto",
"account_role_savingAsset": "Sparekonto",
"account_role_sharedAsset": "Delt aktivakonto",
"clear_location": "T\u00f8m lokasjon",
"account_role_ccAsset": "Kredittkort",
"account_role_cashWalletAsset": "Kontant lommebok",
"daily_budgets": "Daily budgets",
@@ -64,6 +67,24 @@
"quarterly_budgets": "Quarterly budgets",
"half_year_budgets": "Half-yearly budgets",
"yearly_budgets": "Yearly budgets",
"split_transaction_title": "Description of the split transaction",
"errors_submission": "There was something wrong with your submission. Please check out the errors.",
"flash_error": "Feil!",
"store_transaction": "Store transaction",
"flash_success": "Suksess!",
"create_another": "After storing, return here to create another one.",
"update_transaction": "Update transaction",
"after_update_create_another": "After updating, return here to continue editing.",
"reset_after": "Reset form after submission",
"bill_paid_on": "Paid on {date}",
"first_split_decides": "The first split determines the value of this field",
"first_split_overrules_source": "The first split may overrule the source account",
"first_split_overrules_destination": "The first split may overrule the destination account",
"transaction_stored_link": "<a href=\"transactions\/show\/{ID}\">Transaction #{ID} (\"{title}\")<\/a> has been stored.",
"custom_period": "Custom period",
"reset_to_current": "Reset to current period",
"select_period": "Select a period",
"location": "Sted",
"other_budgets": "Custom timed budgets",
"journal_links": "Transaksjonskoblinger",
"go_to_withdrawals": "Go to your withdrawals",

View File

@@ -47,6 +47,8 @@
"go_to_expenses": "Ga naar je uitgaven",
"go_to_bills": "Ga naar je contracten",
"bills": "Contracten",
"last_thirty_days": "Laatste dertig dagen",
"last_seven_days": "Laatste zeven dagen",
"go_to_piggies": "Ga naar je spaarpotjes",
"saved": "Opgeslagen",
"piggy_banks": "Spaarpotjes",
@@ -56,6 +58,7 @@
"account_role_defaultAsset": "Standaard betaalrekening",
"account_role_savingAsset": "Spaarrekening",
"account_role_sharedAsset": "Gedeelde betaalrekening",
"clear_location": "Wis locatie",
"account_role_ccAsset": "Credit card",
"account_role_cashWalletAsset": "Cash",
"daily_budgets": "Dagelijkse budgetten",
@@ -64,6 +67,24 @@
"quarterly_budgets": "Driemaandelijkse budgetten",
"half_year_budgets": "Halfjaarlijkse budgetten",
"yearly_budgets": "Jaarlijkse budgetten",
"split_transaction_title": "Beschrijving van de gesplitste transactie",
"errors_submission": "Er ging iets mis. Check de errors.",
"flash_error": "Fout!",
"store_transaction": "Transactie opslaan",
"flash_success": "Gelukt!",
"create_another": "Terug naar deze pagina voor een nieuwe transactie.",
"update_transaction": "Update transactie",
"after_update_create_another": "Na het opslaan terug om door te gaan met wijzigen.",
"reset_after": "Reset formulier na opslaan",
"bill_paid_on": "Betaald op {date}",
"first_split_decides": "De eerste split bepaalt wat hier staat",
"first_split_overrules_source": "De eerste split kan de bronrekening overschrijven",
"first_split_overrules_destination": "De eerste split kan de doelrekening overschrijven",
"transaction_stored_link": "<a href=\"transactions\/show\/{ID}\">Transactie #{ID} (\"{title}\")<\/a> is opgeslagen.",
"custom_period": "Aangepaste periode",
"reset_to_current": "Reset naar huidige periode",
"select_period": "Selecteer een periode",
"location": "Plaats",
"other_budgets": "Aangepaste budgetten",
"journal_links": "Transactiekoppelingen",
"go_to_withdrawals": "Ga naar je uitgaven",

View File

@@ -47,6 +47,8 @@
"go_to_expenses": "Przejd\u017a do wydatk\u00f3w",
"go_to_bills": "Przejd\u017a do swoich rachunk\u00f3w",
"bills": "Rachunki",
"last_thirty_days": "Ostanie 30 dni",
"last_seven_days": "Ostatnie 7 dni",
"go_to_piggies": "Przejd\u017a do swoich skarbonek",
"saved": "Zapisano",
"piggy_banks": "Skarbonki",
@@ -56,6 +58,7 @@
"account_role_defaultAsset": "Domy\u015blne konto aktyw\u00f3w",
"account_role_savingAsset": "Konto oszcz\u0119dno\u015bciowe",
"account_role_sharedAsset": "Wsp\u00f3\u0142dzielone konto aktyw\u00f3w",
"clear_location": "Wyczy\u015b\u0107 lokalizacj\u0119",
"account_role_ccAsset": "Karta kredytowa",
"account_role_cashWalletAsset": "Portfel got\u00f3wkowy",
"daily_budgets": "Bud\u017cety dzienne",
@@ -64,6 +67,24 @@
"quarterly_budgets": "Bud\u017cety kwartalne",
"half_year_budgets": "Bud\u017cety p\u00f3\u0142roczne",
"yearly_budgets": "Bud\u017cety roczne",
"split_transaction_title": "Opis podzielonej transakcji",
"errors_submission": "Co\u015b posz\u0142o nie tak w czasie zapisu. Prosz\u0119 sprawd\u017a b\u0142\u0119dy.",
"flash_error": "B\u0142\u0105d!",
"store_transaction": "Zapisz transakcj\u0119",
"flash_success": "Sukces!",
"create_another": "Po zapisaniu wr\u00f3\u0107 tutaj, aby utworzy\u0107 kolejny.",
"update_transaction": "Zaktualizuj transakcj\u0119",
"after_update_create_another": "Po aktualizacji wr\u00f3\u0107 tutaj, aby kontynuowa\u0107 edycj\u0119.",
"reset_after": "Wyczy\u015b\u0107 formularz po zapisaniu",
"bill_paid_on": "Zap\u0142acone {date}",
"first_split_decides": "Pierwszy podzia\u0142 okre\u015bla warto\u015b\u0107 tego pola",
"first_split_overrules_source": "Pierwszy podzia\u0142 mo\u017ce nadpisa\u0107 konto \u017ar\u00f3d\u0142owe",
"first_split_overrules_destination": "Pierwszy podzia\u0142 mo\u017ce nadpisa\u0107 konto docelowe",
"transaction_stored_link": "<a href=\"transactions\/show\/{ID}\">Transakcja #{ID} (\"{title}\")<\/a> zosta\u0142a zapisana.",
"custom_period": "Okres niestandardowy",
"reset_to_current": "Przywr\u00f3\u0107 do bie\u017c\u0105cego okresu",
"select_period": "Wybierz okres",
"location": "Lokalizacja",
"other_budgets": "Bud\u017cety niestandardowe",
"journal_links": "Powi\u0105zane transakcje",
"go_to_withdrawals": "Przejd\u017a do swoich wydatk\u00f3w",

View File

@@ -16,12 +16,12 @@
"transaction_journal_extra": "Informa\u00e7\u00e3o extra",
"transaction_journal_meta": "Meta-informa\u00e7\u00e3o",
"basic_journal_information": "Informa\u00e7\u00f5es b\u00e1sicas de transa\u00e7\u00e3o",
"bills_to_pay": "Faturas a pagar",
"bills_to_pay": "Contas a pagar",
"left_to_spend": "Restante para gastar",
"attachments": "Anexos",
"net_worth": "Valor L\u00edquido",
"bill": "Fatura",
"no_bill": "(sem fatura)",
"no_bill": "(sem conta)",
"tags": "Tags",
"internal_reference": "Refer\u00eancia interna",
"external_url": "URL externa",
@@ -41,12 +41,14 @@
"categories": "Categorias",
"go_to_budgets": "V\u00e1 para seus or\u00e7amentos",
"income": "Receita \/ Renda",
"go_to_deposits": "Ir para os dep\u00f3sitos",
"go_to_deposits": "Ir para as entradas",
"go_to_categories": "V\u00e1 para suas categorias",
"expense_accounts": "Contas de despesas",
"go_to_expenses": "Ir para despesas",
"go_to_bills": "V\u00e1 para suas faturas",
"go_to_bills": "V\u00e1 para suas contas",
"bills": "Faturas",
"last_thirty_days": "\u00daltimos 30 dias",
"last_seven_days": "\u00daltimos sete dias",
"go_to_piggies": "V\u00e1 para sua poupan\u00e7a",
"saved": "Salvo",
"piggy_banks": "Cofrinhos",
@@ -56,6 +58,7 @@
"account_role_defaultAsset": "Conta padr\u00e3o",
"account_role_savingAsset": "Conta poupan\u00e7a",
"account_role_sharedAsset": "Contas de ativos compartilhadas",
"clear_location": "Limpar localiza\u00e7\u00e3o",
"account_role_ccAsset": "Cart\u00e3o de cr\u00e9dito",
"account_role_cashWalletAsset": "Carteira de dinheiro",
"daily_budgets": "Or\u00e7amentos di\u00e1rios",
@@ -64,7 +67,25 @@
"quarterly_budgets": "Or\u00e7amentos trimestrais",
"half_year_budgets": "Or\u00e7amentos semestrais",
"yearly_budgets": "Or\u00e7amentos anuais",
"other_budgets": "Custom timed budgets",
"split_transaction_title": "Descri\u00e7\u00e3o da transa\u00e7\u00e3o dividida",
"errors_submission": "H\u00e1 algo de errado com o seu envio. Por favor, verifique os erros abaixo.",
"flash_error": "Erro!",
"store_transaction": "Salvar transa\u00e7\u00e3o",
"flash_success": "Sucesso!",
"create_another": "Depois de armazenar, retorne aqui para criar outro.",
"update_transaction": "Atualizar transa\u00e7\u00e3o",
"after_update_create_another": "Depois de atualizar, retorne aqui para continuar editando.",
"reset_after": "Resetar o formul\u00e1rio ap\u00f3s o envio",
"bill_paid_on": "Pago em {date}",
"first_split_decides": "A primeira divis\u00e3o determina o valor deste campo",
"first_split_overrules_source": "A primeira divis\u00e3o pode anular a conta de origem",
"first_split_overrules_destination": "A primeira divis\u00e3o pode anular a conta de destino",
"transaction_stored_link": "<a href=\"transactions\/show\/{ID}\">Transa\u00e7\u00e3o #{ID} (\"{title}\")<\/a> foi salva.",
"custom_period": "Per\u00edodo personalizado",
"reset_to_current": "Redefinir para o per\u00edodo atual",
"select_period": "Selecione um per\u00edodo",
"location": "Localiza\u00e7\u00e3o",
"other_budgets": "Or\u00e7amentos de per\u00edodos personalizados",
"journal_links": "Transa\u00e7\u00f5es ligadas",
"go_to_withdrawals": "V\u00e1 para seus saques",
"revenue_accounts": "Contas de receitas",

View File

@@ -0,0 +1,118 @@
{
"firefly": {
"Transfer": "Transferencia",
"Withdrawal": "Levantamento",
"Deposit": "Deposito",
"date_and_time": "Data e hora",
"no_currency": "(sem moeda)",
"date": "Data",
"time": "Hora",
"no_budget": "(sem orcamento)",
"destination_account": "Conta de destino",
"source_account": "Conta de origem",
"single_split": "Dividir",
"create_new_transaction": "Criar uma nova transa\u00e7\u00e3o",
"balance": "Saldo",
"transaction_journal_extra": "Informa\u00e7\u00f5es extra",
"transaction_journal_meta": "Informacao",
"basic_journal_information": "Informa\u00e7\u00f5es b\u00e1sicas de transa\u00e7\u00e3o",
"bills_to_pay": "Contas por pagar",
"left_to_spend": "Restante para gastar",
"attachments": "Anexos",
"net_worth": "Patrimonio liquido",
"bill": "Conta",
"no_bill": "(sem contas)",
"tags": "Tags",
"internal_reference": "Refer\u00eancia interna",
"external_url": "URL Externo",
"no_piggy_bank": "(nenhum mealheiro)",
"paid": "Pago",
"notes": "Notas",
"yourAccounts": "As suas contas",
"go_to_asset_accounts": "Ver as contas de activos",
"transaction_table_description": "Uma tabela com as suas transac\u00e7\u00f5es",
"account": "Conta",
"description": "Descricao",
"amount": "Montante",
"budget": "Orcamento",
"category": "Categoria",
"opposing_account": "Conta oposta",
"budgets": "Orcamentos",
"categories": "Categorias",
"go_to_budgets": "Ir para orcamentos",
"income": "Receita \/ renda",
"go_to_deposits": "Ir para dep\u00f3sitos",
"go_to_categories": "Ir para categorias",
"expense_accounts": "Conta de despesas",
"go_to_expenses": "Ir para despesas",
"go_to_bills": "Ir para contas",
"bills": "Contas",
"last_thirty_days": "\u00daltimos trinta dias",
"last_seven_days": "\u00daltimos sete dias",
"go_to_piggies": "Ir para mealheiros",
"saved": "Guardado",
"piggy_banks": "Mealheiros",
"piggy_bank": "Mealheiro",
"amounts": "Montantes",
"Default asset account": "Conta de activos padr\u00e3o",
"account_role_defaultAsset": "Conta de activos padr\u00e3o",
"account_role_savingAsset": "Contas poupan\u00e7a",
"account_role_sharedAsset": "Conta de activos partilhados",
"clear_location": "Limpar localiza\u00e7\u00e3o",
"account_role_ccAsset": "Cart\u00e3o de credito",
"account_role_cashWalletAsset": "Carteira de dinheiro",
"daily_budgets": "Or\u00e7amento di\u00e1rio",
"weekly_budgets": "Or\u00e7amento semanal",
"monthly_budgets": "Or\u00e7amento mensal",
"quarterly_budgets": "Or\u00e7amento trimestral",
"half_year_budgets": "Or\u00e7amento semestral",
"yearly_budgets": "Or\u00e7amento anual",
"split_transaction_title": "Descri\u00e7\u00e3o da transac\u00e7\u00e3o dividida",
"errors_submission": "Aconteceu algo errado com a sua submiss\u00e3o. Por favor, verifique os erros.",
"flash_error": "Erro!",
"store_transaction": "Guardar transa\u00e7\u00e3o",
"flash_success": "Sucesso!",
"create_another": "Depois de guardar, voltar aqui para criar outra.",
"update_transaction": "Actualizar transac\u00e7\u00e3o",
"after_update_create_another": "Ap\u00f3s a atualiza\u00e7\u00e3o, regresse aqui para continuar a editar.",
"reset_after": "Repor o formul\u00e1rio ap\u00f3s o envio",
"bill_paid_on": "Pago a {date}",
"first_split_decides": "A primeira divis\u00e3o determina o valor deste campo",
"first_split_overrules_source": "A primeira divis\u00e3o pode anular a conta de origem",
"first_split_overrules_destination": "A primeira divis\u00e3o pode anular a conta de destino",
"transaction_stored_link": "<a href=\"transactions\/show\/{ID}\">Transa\u00e7\u00e3o #{ID} (\"{title}\")<\/a> foi guardada.",
"custom_period": "Per\u00edodo personalizado",
"reset_to_current": "Reiniciar o per\u00edodo personalizado",
"select_period": "Selecionar um per\u00edodo",
"location": "Localiza\u00e7\u00e3o",
"other_budgets": "Or\u00e7amentos de tempo personalizado",
"journal_links": "Liga\u00e7\u00f5es de transac\u00e7\u00e3o",
"go_to_withdrawals": "Ir para os seus levantamentos",
"revenue_accounts": "Conta de receitas",
"add_another_split": "Adicionar outra divis\u00e3o"
},
"list": {
"piggy_bank": "Mealheiro",
"percentage": "%.",
"amount": "Montante",
"name": "Nome",
"role": "Regra",
"iban": "IBAN",
"lastActivity": "Ultima actividade",
"currentBalance": "Saldo actual",
"balanceDiff": "Diferenca de saldo",
"next_expected_match": "Proxima correspondencia esperada"
},
"config": {
"html_language": "pt"
},
"form": {
"foreign_amount": "Montante estrangeiro",
"interest_date": "Data de juros",
"book_date": "Data de registo",
"process_date": "Data de processamento",
"due_date": "Data de vencimento",
"payment_date": "Data de pagamento",
"invoice_date": "Data da factura"
}
}

View File

@@ -47,6 +47,8 @@
"go_to_expenses": "Go to expenses",
"go_to_bills": "Mergi la facturi",
"bills": "Facturi",
"last_thirty_days": "Ultimele 30 de zile",
"last_seven_days": "Ultimele 7 zile",
"go_to_piggies": "Mergi la pu\u0219culi\u021b\u0103",
"saved": "Salvat",
"piggy_banks": "Pu\u0219culi\u021b\u0103",
@@ -56,6 +58,7 @@
"account_role_defaultAsset": "Contul implicit activ",
"account_role_savingAsset": "Cont de economii",
"account_role_sharedAsset": "Contul de active partajat",
"clear_location": "\u0218terge\u021bi loca\u021bia",
"account_role_ccAsset": "Card de credit",
"account_role_cashWalletAsset": "Cash - Numerar",
"daily_budgets": "Daily budgets",
@@ -64,6 +67,24 @@
"quarterly_budgets": "Quarterly budgets",
"half_year_budgets": "Half-yearly budgets",
"yearly_budgets": "Yearly budgets",
"split_transaction_title": "Descrierea tranzac\u021biei divizate",
"errors_submission": "There was something wrong with your submission. Please check out the errors.",
"flash_error": "Eroare!",
"store_transaction": "Store transaction",
"flash_success": "Succes!",
"create_another": "Dup\u0103 stocare, reveni\u021bi aici pentru a crea alta.",
"update_transaction": "Actualiza\u021bi tranzac\u021bia",
"after_update_create_another": "Dup\u0103 actualizare, reveni\u021bi aici pentru a continua editarea.",
"reset_after": "Reseta\u021bi formularul dup\u0103 trimitere",
"bill_paid_on": "Paid on {date}",
"first_split_decides": "The first split determines the value of this field",
"first_split_overrules_source": "The first split may overrule the source account",
"first_split_overrules_destination": "The first split may overrule the destination account",
"transaction_stored_link": "<a href=\"transactions\/show\/{ID}\">Tranzac\u021bia #{ID} (\"{title}\")<\/a> a fost stocat\u0103.",
"custom_period": "Custom period",
"reset_to_current": "Reset to current period",
"select_period": "Select a period",
"location": "Loca\u021bie",
"other_budgets": "Custom timed budgets",
"journal_links": "Link-uri de tranzac\u021bii",
"go_to_withdrawals": "Go to your withdrawals",

View File

@@ -23,8 +23,8 @@
"bill": "\u0421\u0447\u0451\u0442 \u043a \u043e\u043f\u043b\u0430\u0442\u0435",
"no_bill": "(\u043d\u0435\u0442 \u0441\u0447\u0451\u0442\u0430 \u043d\u0430 \u043e\u043f\u043b\u0430\u0442\u0443)",
"tags": "\u041c\u0435\u0442\u043a\u0438",
"internal_reference": "Internal reference",
"external_url": "External URL",
"internal_reference": "\u0412\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u044f\u044f \u0441\u0441\u044b\u043b\u043a\u0430",
"external_url": "\u0412\u043d\u0435\u0448\u043d\u0438\u0439 URL-\u0430\u0434\u0440\u0435\u0441",
"no_piggy_bank": "(\u043d\u0435\u0442 \u043a\u043e\u043f\u0438\u043b\u043a\u0438)",
"paid": "\u041e\u043f\u043b\u0430\u0447\u0435\u043d\u043e",
"notes": "\u0417\u0430\u043c\u0435\u0442\u043a\u0438",
@@ -47,6 +47,8 @@
"go_to_expenses": "\u041f\u0435\u0440\u0435\u0439\u0442\u0438 \u043a \u0440\u0430\u0441\u0445\u043e\u0434\u0430\u043c",
"go_to_bills": "\u041f\u0435\u0440\u0435\u0439\u0442\u0438 \u043a \u0432\u0430\u0448\u0438\u043c \u0441\u0447\u0435\u0442\u0430\u043c \u043d\u0430 \u043e\u043f\u043b\u0430\u0442\u0443",
"bills": "\u0421\u0447\u0435\u0442\u0430 \u043a \u043e\u043f\u043b\u0430\u0442\u0435",
"last_thirty_days": "\u041f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0435 30 \u0434\u043d\u0435\u0439",
"last_seven_days": "\u041f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0435 7 \u0434\u043d\u0435\u0439",
"go_to_piggies": "\u041f\u0435\u0440\u0435\u0439\u0442\u0438 \u043a \u0432\u0430\u0448\u0438\u043c \u043a\u043e\u043f\u0438\u043b\u043a\u0430\u043c",
"saved": "\u0421\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u043e",
"piggy_banks": "\u041a\u043e\u043f\u0438\u043b\u043a\u0438",
@@ -56,19 +58,38 @@
"account_role_defaultAsset": "\u0421\u0447\u0451\u0442 \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e",
"account_role_savingAsset": "\u0421\u0431\u0435\u0440\u0435\u0433\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u0441\u0447\u0435\u0442",
"account_role_sharedAsset": "\u041e\u0431\u0449\u0438\u0439 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u0441\u0447\u0451\u0442",
"clear_location": "\u041e\u0447\u0438\u0441\u0442\u0438\u0442\u044c \u043c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435",
"account_role_ccAsset": "\u041a\u0440\u0435\u0434\u0438\u0442\u043d\u0430\u044f \u043a\u0430\u0440\u0442\u0430",
"account_role_cashWalletAsset": "\u041a\u043e\u0448\u0435\u043b\u0451\u043a \u0441 \u043d\u0430\u043b\u0438\u0447\u043d\u044b\u043c\u0438",
"account_role_cashWalletAsset": "\u041d\u0430\u043b\u0438\u0447\u043d\u044b\u0435",
"daily_budgets": "\u0411\u044e\u0434\u0436\u0435\u0442\u044b \u043d\u0430 \u0434\u0435\u043d\u044c",
"weekly_budgets": "\u0411\u044e\u0434\u0436\u0435\u0442\u044b \u043d\u0430 \u043d\u0435\u0434\u0435\u043b\u044e",
"monthly_budgets": "\u0411\u044e\u0434\u0436\u0435\u0442\u044b \u043d\u0430 \u043c\u0435\u0441\u044f\u0446",
"quarterly_budgets": "\u0411\u044e\u0434\u0436\u0435\u0442\u044b \u043d\u0430 \u043a\u0432\u0430\u0440\u0442\u0430\u043b",
"half_year_budgets": "\u0411\u044e\u0434\u0436\u0435\u0442\u044b \u043d\u0430 \u043f\u043e\u043b\u0433\u043e\u0434\u0430",
"yearly_budgets": "\u0413\u043e\u0434\u043e\u0432\u044b\u0435 \u0431\u044e\u0434\u0436\u0435\u0442\u044b",
"split_transaction_title": "\u041e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0440\u0430\u0437\u0434\u0435\u043b\u0451\u043d\u043d\u043e\u0439 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438",
"errors_submission": "\u041f\u0440\u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0435 \u0447\u0442\u043e-\u0442\u043e \u043f\u043e\u0448\u043b\u043e \u043d\u0435 \u0442\u0430\u043a. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u043e\u0448\u0438\u0431\u043a\u0438 \u043d\u0438\u0436\u0435.",
"flash_error": "\u041e\u0448\u0438\u0431\u043a\u0430!",
"store_transaction": "\u0421\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044e",
"flash_success": "\u0423\u0441\u043f\u0435\u0448\u043d\u043e!",
"create_another": "\u041f\u043e\u0441\u043b\u0435 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0432\u0435\u0440\u043d\u0443\u0442\u044c\u0441\u044f \u0441\u044e\u0434\u0430 \u0438 \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0435\u0449\u0451 \u043e\u0434\u043d\u0443 \u0430\u043d\u0430\u043b\u043e\u0433\u0438\u0447\u043d\u0443\u044e \u0437\u0430\u043f\u0438\u0441\u044c.",
"update_transaction": "\u041e\u0431\u043d\u043e\u0432\u0438\u0442\u044c \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044e",
"after_update_create_another": "\u041f\u043e\u0441\u043b\u0435 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u0432\u0435\u0440\u043d\u0438\u0442\u0435\u0441\u044c \u0441\u044e\u0434\u0430, \u0447\u0442\u043e\u0431\u044b \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0438\u0442\u044c \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435.",
"reset_after": "\u0421\u0431\u0440\u043e\u0441\u0438\u0442\u044c \u0444\u043e\u0440\u043c\u0443 \u043f\u043e\u0441\u043b\u0435 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438",
"bill_paid_on": "\u041e\u043f\u043b\u0430\u0447\u0435\u043d\u043e {date}",
"first_split_decides": "\u0412 \u0434\u0430\u043d\u043d\u043e\u043c \u043f\u043e\u043b\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0438\u0437 \u043f\u0435\u0440\u0432\u043e\u0439 \u0447\u0430\u0441\u0442\u0438 \u0440\u0430\u0437\u0434\u0435\u043b\u0435\u043d\u043d\u043e\u0439 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438",
"first_split_overrules_source": "\u0417\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0438\u0437 \u043f\u0435\u0440\u0432\u043e\u0439 \u0447\u0430\u0441\u0442\u0438 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438 \u043c\u043e\u0436\u0435\u0442 \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u044c \u0441\u0447\u0435\u0442 \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u0430",
"first_split_overrules_destination": "\u0417\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0438\u0437 \u043f\u0435\u0440\u0432\u043e\u0439 \u0447\u0430\u0441\u0442\u0438 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438 \u043c\u043e\u0436\u0435\u0442 \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u044c \u0441\u0447\u0435\u0442 \u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f",
"transaction_stored_link": "<a href=\"transactions\/show\/{ID}\">\u0422\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044f #{ID} (\"{title}\")<\/a> \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0430.",
"custom_period": "Custom period",
"reset_to_current": "Reset to current period",
"select_period": "Select a period",
"location": "\u0420\u0430\u0437\u043c\u0435\u0449\u0435\u043d\u0438\u0435",
"other_budgets": "\u0411\u044e\u0434\u0436\u0435\u0442\u044b \u043d\u0430 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u043b\u044c\u043d\u044b\u0439 \u043e\u0442\u0440\u0435\u0437\u043e\u043a \u0432\u0440\u0435\u043c\u0435\u043d\u0438",
"journal_links": "\u0421\u0432\u044f\u0437\u0438 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438",
"go_to_withdrawals": "\u041f\u0435\u0440\u0435\u0439\u0442\u0438 \u043a \u0432\u0430\u0448\u0438\u043c \u0440\u0430\u0441\u0445\u043e\u0434\u0430\u043c",
"revenue_accounts": "\u0421\u0447\u0435\u0442\u0430 \u0434\u043e\u0445\u043e\u0434\u043e\u0432",
"add_another_split": "\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043d\u043e\u0432\u0443\u044e \u0447\u0430\u0441\u0442\u044c"
"add_another_split": "\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0435\u0449\u0435 \u043e\u0434\u043d\u0443 \u0447\u0430\u0441\u0442\u044c"
},
"list": {
"piggy_bank": "\u041a\u043e\u043f\u0438\u043b\u043a\u0430",
@@ -87,10 +108,10 @@
},
"form": {
"foreign_amount": "\u0421\u0443\u043c\u043c\u0430 \u0432 \u0438\u043d\u043e\u0441\u0442\u0440\u0430\u043d\u043d\u043e\u0439 \u0432\u0430\u043b\u044e\u0442\u0435",
"interest_date": "\u0414\u0430\u0442\u0430 \u0432\u044b\u043f\u043b\u0430\u0442\u044b",
"interest_date": "\u0414\u0430\u0442\u0430 \u043d\u0430\u0447\u0438\u0441\u043b\u0435\u043d\u0438\u044f \u043f\u0440\u043e\u0446\u0435\u043d\u0442\u043e\u0432",
"book_date": "\u0414\u0430\u0442\u0430 \u0431\u0440\u043e\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f",
"process_date": "\u0414\u0430\u0442\u0430 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438",
"due_date": "\u0421\u0440\u043e\u043a",
"due_date": "\u0421\u0440\u043e\u043a \u043e\u043f\u043b\u0430\u0442\u044b",
"payment_date": "\u0414\u0430\u0442\u0430 \u043f\u043b\u0430\u0442\u0435\u0436\u0430",
"invoice_date": "\u0414\u0430\u0442\u0430 \u0432\u044b\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0441\u0447\u0451\u0442\u0430"
}

View File

@@ -47,6 +47,8 @@
"go_to_expenses": "Zobrazi\u0165 v\u00fddavky",
"go_to_bills": "Zobrazi\u0165 \u00fa\u010dty",
"bills": "\u00da\u010dty",
"last_thirty_days": "Uplynul\u00fdch 30 dn\u00ed",
"last_seven_days": "Uplynul\u00fdch 7 dn\u00ed",
"go_to_piggies": "Zobrazi\u0165 pokladni\u010dky",
"saved": "Ulo\u017een\u00e9",
"piggy_banks": "Pokladni\u010dky",
@@ -56,6 +58,7 @@
"account_role_defaultAsset": "Predvolen\u00fd \u00fa\u010det akt\u00edv",
"account_role_savingAsset": "\u0160etriaci \u00fa\u010det",
"account_role_sharedAsset": "Zdie\u013ean\u00fd \u00fa\u010det akt\u00edv",
"clear_location": "Odstr\u00e1ni\u0165 poz\u00edciu",
"account_role_ccAsset": "Kreditn\u00e1 karta",
"account_role_cashWalletAsset": "Pe\u0148a\u017eenka",
"daily_budgets": "Denn\u00e9 rozpo\u010dty",
@@ -64,6 +67,24 @@
"quarterly_budgets": "\u0160tvr\u0165ro\u010dn\u00e9 rozpo\u010dty",
"half_year_budgets": "Polro\u010dn\u00e9 rozpo\u010dty",
"yearly_budgets": "Ro\u010dn\u00e9 rozpo\u010dty",
"split_transaction_title": "Popis roz\u00fa\u010dtovania",
"errors_submission": "Pri odosielan\u00ed sa nie\u010do nepodarilo. Skontrolujte pros\u00edm chyby.",
"flash_error": "Chyba!",
"store_transaction": "Ulo\u017ei\u0165 transakciu",
"flash_success": "Hotovo!",
"create_another": "Po ulo\u017een\u00ed sa vr\u00e1ti\u0165 sp\u00e4\u0165 sem a vytvori\u0165 \u010fal\u0161\u00ed.",
"update_transaction": "Upravi\u0165 transakciu",
"after_update_create_another": "Po aktualiz\u00e1cii sa vr\u00e1ti\u0165 sp\u00e4\u0165 a pokra\u010dova\u0165 v \u00faprav\u00e1ch.",
"reset_after": "Po odoslan\u00ed vynulova\u0165 formul\u00e1r",
"bill_paid_on": "Uhraden\u00e9 {date}",
"first_split_decides": "The first split determines the value of this field",
"first_split_overrules_source": "The first split may overrule the source account",
"first_split_overrules_destination": "The first split may overrule the destination account",
"transaction_stored_link": "<a href=\"transactions\/show\/{ID}\">Transakcia #{ID} (\"{title}\")<\/a> bola ulo\u017een\u00e1.",
"custom_period": "Vlastn\u00e9 obdobie",
"reset_to_current": "Obnovi\u0165 na aktu\u00e1lne obdobie",
"select_period": "Vyberte obdobie",
"location": "Poloha",
"other_budgets": "\u0160pecifick\u00e9 \u010dasovan\u00e9 rozpo\u010dty",
"journal_links": "Prepojenia transakcie",
"go_to_withdrawals": "Zobrazi\u0165 v\u00fdbery",

View File

@@ -47,6 +47,8 @@
"go_to_expenses": "G\u00e5 till utgifter",
"go_to_bills": "G\u00e5 till dina notor",
"bills": "Notor",
"last_thirty_days": "Senaste 30 dagarna",
"last_seven_days": "Senaste 7 dagarna",
"go_to_piggies": "G\u00e5 till dina sparb\u00f6ssor",
"saved": "Sparad",
"piggy_banks": "Spargrisar",
@@ -56,6 +58,7 @@
"account_role_defaultAsset": "F\u00f6rvalt tillg\u00e5ngskonto",
"account_role_savingAsset": "Sparkonto",
"account_role_sharedAsset": "Delat tillg\u00e5ngskonto",
"clear_location": "Rena plats",
"account_role_ccAsset": "Kreditkort",
"account_role_cashWalletAsset": "Pl\u00e5nbok",
"daily_budgets": "Dagliga budgetar",
@@ -64,6 +67,24 @@
"quarterly_budgets": "Kvartalsbudgetar",
"half_year_budgets": "Halv\u00e5rsbudgetar",
"yearly_budgets": "\u00c5rliga budgetar",
"split_transaction_title": "Beskrivning av delad transaktion",
"errors_submission": "N\u00e5got fel uppstod med inskickningen. V\u00e4nligen kontrollera felen nedan.",
"flash_error": "Fel!",
"store_transaction": "Store transaction",
"flash_success": "Slutf\u00f6rd!",
"create_another": "Efter sparat, \u00e5terkom hit f\u00f6r att skapa ytterligare en.",
"update_transaction": "Uppdatera transaktion",
"after_update_create_another": "Efter uppdaterat, \u00e5terkom hit f\u00f6r att forts\u00e4tta redigera.",
"reset_after": "\u00c5terst\u00e4ll formul\u00e4r efter inskickat",
"bill_paid_on": "Paid on {date}",
"first_split_decides": "The first split determines the value of this field",
"first_split_overrules_source": "The first split may overrule the source account",
"first_split_overrules_destination": "The first split may overrule the destination account",
"transaction_stored_link": "<a href=\"transactions\/show\/{ID}\">Transaktion #{ID} (\"{title}\")<\/a> sparades.",
"custom_period": "Custom period",
"reset_to_current": "Reset to current period",
"select_period": "Select a period",
"location": "Plats",
"other_budgets": "Anpassade tidsinst\u00e4llda budgetar",
"journal_links": "Transaktionsl\u00e4nkar",
"go_to_withdrawals": "G\u00e5 till dina uttag",

View File

@@ -47,6 +47,8 @@
"go_to_expenses": "Go to expenses",
"go_to_bills": "\u0110i \u0111\u1ebfn h\u00f3a \u0111\u01a1n c\u1ee7a b\u1ea1n",
"bills": "H\u00f3a \u0111\u01a1n",
"last_thirty_days": "Ba m\u01b0\u01a1i ng\u00e0y g\u1ea7n \u0111\u00e2y",
"last_seven_days": "B\u1ea3y ng\u00e0y g\u1ea7n \u0111\u00e2y",
"go_to_piggies": "T\u1edbi heo \u0111\u1ea5t c\u1ee7a b\u1ea1n",
"saved": "Saved",
"piggy_banks": "Heo \u0111\u1ea5t",
@@ -56,6 +58,7 @@
"account_role_defaultAsset": "t\u00e0i kho\u1ea3n m\u1eb7c \u0111\u1ecbnh",
"account_role_savingAsset": "T\u00e0i kho\u1ea3n ti\u1ebft ki\u1ec7m",
"account_role_sharedAsset": "t\u00e0i kho\u1ea3n d\u00f9ng chung",
"clear_location": "X\u00f3a v\u1ecb tr\u00ed",
"account_role_ccAsset": "Th\u1ebb t\u00edn d\u1ee5ng",
"account_role_cashWalletAsset": "V\u00ed ti\u1ec1n m\u1eb7t",
"daily_budgets": "Daily budgets",
@@ -64,6 +67,24 @@
"quarterly_budgets": "Quarterly budgets",
"half_year_budgets": "Half-yearly budgets",
"yearly_budgets": "Yearly budgets",
"split_transaction_title": "M\u00f4 t\u1ea3 giao d\u1ecbch t\u00e1ch",
"errors_submission": "There was something wrong with your submission. Please check out the errors.",
"flash_error": "L\u1ed7i!",
"store_transaction": "Store transaction",
"flash_success": "Th\u00e0nh c\u00f4ng!",
"create_another": "Sau khi l\u01b0u tr\u1eef, quay tr\u1edf l\u1ea1i \u0111\u00e2y \u0111\u1ec3 t\u1ea1o m\u1ed9t c\u00e1i kh\u00e1c.",
"update_transaction": "C\u1eadp nh\u1eadt giao d\u1ecbch",
"after_update_create_another": "Sau khi c\u1eadp nh\u1eadt, quay l\u1ea1i \u0111\u00e2y \u0111\u1ec3 ti\u1ebfp t\u1ee5c ch\u1ec9nh s\u1eeda.",
"reset_after": "\u0110\u1eb7t l\u1ea1i m\u1eabu sau khi g\u1eedi",
"bill_paid_on": "Paid on {date}",
"first_split_decides": "The first split determines the value of this field",
"first_split_overrules_source": "The first split may overrule the source account",
"first_split_overrules_destination": "The first split may overrule the destination account",
"transaction_stored_link": "<a href=\"transactions\/show\/{ID}\">Giao d\u1ecbch #{ID} (\"{title}\")<\/a> \u0111\u00e3 \u0111\u01b0\u1ee3c l\u01b0u tr\u1eef.",
"custom_period": "Custom period",
"reset_to_current": "Reset to current period",
"select_period": "Select a period",
"location": "V\u1ecb tr\u00ed",
"other_budgets": "Custom timed budgets",
"journal_links": "Li\u00ean k\u1ebft giao d\u1ecbch",
"go_to_withdrawals": "Go to your withdrawals",

View File

@@ -1,74 +1,95 @@
{
"firefly": {
"Transfer": "\u8f6c\u5e10",
"Transfer": "\u8f6c\u8d26",
"Withdrawal": "\u63d0\u6b3e",
"Deposit": "\u5b58\u6b3e",
"date_and_time": "Date and time",
"Deposit": "\u6536\u5165",
"date_and_time": "\u65e5\u671f\u548c\u65f6\u95f4",
"no_currency": "(\u6ca1\u6709\u8d27\u5e01)",
"date": "\u65e5\u671f",
"time": "Time",
"time": "\u65f6\u95f4",
"no_budget": "(\u65e0\u9884\u7b97)",
"destination_account": "\u76ee\u6807\u5e10\u6237",
"source_account": "\u6765\u6e90\u5e10\u6237",
"single_split": "Split",
"create_new_transaction": "\u65b0\u5efa\u4ea4\u6613",
"destination_account": "\u76ee\u6807\u8d26\u6237",
"source_account": "\u6765\u6e90\u8d26\u6237",
"single_split": "\u62c6\u5206",
"create_new_transaction": "\u521b\u5efa\u65b0\u4ea4\u6613",
"balance": "\u4f59\u989d",
"transaction_journal_extra": "Extra information",
"transaction_journal_meta": "\u540e\u8bbe\u8d44\u8baf",
"basic_journal_information": "Basic transaction information",
"bills_to_pay": "\u5f85\u4ed8\u5e10\u5355",
"left_to_spend": "\u5269\u4f59\u53ef\u82b1\u8d39",
"attachments": "\u9644\u52a0\u6863\u6848",
"net_worth": "\u51c0\u503c",
"bill": "\u5e10\u5355",
"no_bill": "(no bill)",
"transaction_journal_extra": "\u989d\u5916\u4fe1\u606f",
"transaction_journal_meta": "\u5143\u4fe1\u606f",
"basic_journal_information": "\u57fa\u7840\u4ea4\u6613\u4fe1\u606f",
"bills_to_pay": "\u5f85\u4ed8\u8d26\u5355",
"left_to_spend": "\u5269\u4f59\u652f\u51fa",
"attachments": "\u9644\u4ef6",
"net_worth": "\u51c0\u8d44\u4ea7",
"bill": "\u8d26\u5355",
"no_bill": "(\u65e0\u8d26\u5355)",
"tags": "\u6807\u7b7e",
"internal_reference": "\u5185\u90e8\u53c2\u8003",
"internal_reference": "\u5185\u90e8\u5f15\u7528",
"external_url": "\u5916\u90e8\u94fe\u63a5",
"no_piggy_bank": "\uff08\u65e0\u5b58\u94b1\u7f50\uff09",
"no_piggy_bank": "(\u65e0\u5b58\u94b1\u7f50)",
"paid": "\u5df2\u4ed8\u6b3e",
"notes": "\u6ce8\u91ca",
"yourAccounts": "\u60a8\u7684\u5e10\u6237",
"go_to_asset_accounts": "\u68c0\u89c6\u60a8\u7684\u8d44\u4ea7\u5e10\u6237",
"transaction_table_description": "A table containing your transactions",
"account": "\u5e10\u6237",
"notes": "\u5907\u6ce8",
"yourAccounts": "\u60a8\u7684\u8d26\u6237",
"go_to_asset_accounts": "\u67e5\u770b\u60a8\u7684\u8d44\u4ea7\u8d26\u6237",
"transaction_table_description": "\u5305\u542b\u60a8\u4ea4\u6613\u7684\u8868\u683c",
"account": "\u8d26\u6237",
"description": "\u63cf\u8ff0",
"amount": "\u91d1\u989d",
"budget": "\u9884\u7b97",
"category": "\u5206\u7c7b",
"opposing_account": "Opposing account",
"opposing_account": "\u5bf9\u65b9\u8d26\u6237",
"budgets": "\u9884\u7b97",
"categories": "\u5206\u7c7b",
"go_to_budgets": "\u524d\u5f80\u60a8\u7684\u9884\u7b97",
"income": "\u6536\u5165 \/ \u6240\u5f97",
"go_to_deposits": "Go to deposits",
"income": "\u6536\u5165",
"go_to_deposits": "\u524d\u5f80\u6536\u5165",
"go_to_categories": "\u524d\u5f80\u60a8\u7684\u5206\u7c7b",
"expense_accounts": "\u652f\u51fa\u5e10\u6237",
"go_to_expenses": "Go to expenses",
"go_to_bills": "\u524d\u5f80\u60a8\u7684\u5e10\u5355",
"bills": "\u5e10\u5355",
"expense_accounts": "\u652f\u51fa\u8d26\u6237",
"go_to_expenses": "\u524d\u5f80\u652f\u51fa",
"go_to_bills": "\u524d\u5f80\u8d26\u5355",
"bills": "\u8d26\u5355",
"last_thirty_days": "\u6700\u8fd1 30 \u5929",
"last_seven_days": "\u6700\u8fd1 7 \u5929",
"go_to_piggies": "\u524d\u5f80\u60a8\u7684\u5b58\u94b1\u7f50",
"saved": "\u5df2\u4fdd\u5b58",
"piggy_banks": "\u5b58\u94b1\u7f50",
"piggy_bank": "\u5b58\u94b1\u7f50",
"amounts": "Amounts",
"Default asset account": "\u9884\u8bbe\u8d44\u4ea7\u5e10\u6237",
"account_role_defaultAsset": "\u9884\u8bbe\u8d44\u4ea7\u5e10\u6237",
"account_role_savingAsset": "\u50a8\u84c4\u5e10\u6237",
"account_role_sharedAsset": "\u5171\u7528\u8d44\u4ea7\u5e10\u6237",
"amounts": "\u91d1\u989d",
"Default asset account": "\u9ed8\u8ba4\u8d44\u4ea7\u8d26\u6237",
"account_role_defaultAsset": "\u9ed8\u8ba4\u8d44\u4ea7\u8d26\u6237",
"account_role_savingAsset": "\u50a8\u84c4\u8d26\u6237",
"account_role_sharedAsset": "\u5171\u7528\u8d44\u4ea7\u8d26\u6237",
"clear_location": "\u6e05\u9664\u4f4d\u7f6e",
"account_role_ccAsset": "\u4fe1\u7528\u5361",
"account_role_cashWalletAsset": "\u73b0\u91d1\u94b1\u5305",
"daily_budgets": "Daily budgets",
"weekly_budgets": "Weekly budgets",
"monthly_budgets": "Monthly budgets",
"quarterly_budgets": "Quarterly budgets",
"half_year_budgets": "Half-yearly budgets",
"yearly_budgets": "Yearly budgets",
"other_budgets": "Custom timed budgets",
"journal_links": "\u4ea4\u6613\u8fde\u7ed3",
"go_to_withdrawals": "Go to your withdrawals",
"revenue_accounts": "\u6536\u5165\u5e10\u6237",
"add_another_split": "\u589e\u52a0\u62c6\u5206"
"daily_budgets": "\u6bcf\u65e5\u9884\u7b97",
"weekly_budgets": "\u6bcf\u5468\u9884\u7b97",
"monthly_budgets": "\u6bcf\u6708\u9884\u7b97",
"quarterly_budgets": "\u6bcf\u5b63\u5ea6\u9884\u7b97",
"half_year_budgets": "\u6bcf\u534a\u5e74\u9884\u7b97",
"yearly_budgets": "\u6bcf\u5e74\u9884\u7b97",
"split_transaction_title": "\u62c6\u5206\u4ea4\u6613\u7684\u63cf\u8ff0",
"errors_submission": "\u60a8\u63d0\u4ea4\u7684\u5185\u5bb9\u6709\u8bef\uff0c\u8bf7\u68c0\u67e5\u9519\u8bef\u4fe1\u606f\u3002",
"flash_error": "\u9519\u8bef\uff01",
"store_transaction": "\u4fdd\u5b58\u4ea4\u6613",
"flash_success": "\u6210\u529f\uff01",
"create_another": "\u4fdd\u5b58\u540e\uff0c\u8fd4\u56de\u6b64\u9875\u9762\u4ee5\u521b\u5efa\u65b0\u8bb0\u5f55",
"update_transaction": "\u66f4\u65b0\u4ea4\u6613",
"after_update_create_another": "\u66f4\u65b0\u540e\uff0c\u8fd4\u56de\u6b64\u9875\u9762\u7ee7\u7eed\u7f16\u8f91\u3002",
"reset_after": "\u63d0\u4ea4\u540e\u91cd\u7f6e\u8868\u5355",
"bill_paid_on": "\u652f\u4ed8\u4e8e {date}",
"first_split_decides": "\u9996\u7b14\u62c6\u5206\u51b3\u5b9a\u6b64\u5b57\u6bb5\u7684\u503c",
"first_split_overrules_source": "\u9996\u7b14\u62c6\u5206\u53ef\u80fd\u8986\u76d6\u6765\u6e90\u8d26\u6237",
"first_split_overrules_destination": "\u9996\u7b14\u62c6\u5206\u53ef\u80fd\u8986\u76d6\u76ee\u6807\u8d26\u6237",
"transaction_stored_link": "<a href=\"transactions\/show\/{ID}\">\u4ea4\u6613 #{ID} (\u201c{title}\u201d)<\/a> \u5df2\u4fdd\u5b58\u3002",
"custom_period": "\u81ea\u5b9a\u4e49\u5468\u671f",
"reset_to_current": "\u91cd\u7f6e\u4e3a\u5f53\u524d\u5468\u671f",
"select_period": "\u9009\u62e9\u5468\u671f",
"location": "\u4f4d\u7f6e",
"other_budgets": "\u81ea\u5b9a\u4e49\u533a\u95f4\u9884\u7b97",
"journal_links": "\u4ea4\u6613\u5173\u8054",
"go_to_withdrawals": "\u524d\u5f80\u652f\u51fa",
"revenue_accounts": "\u6536\u5165\u8d26\u6237",
"add_another_split": "\u589e\u52a0\u53e6\u4e00\u7b14\u62c6\u5206"
},
"list": {
"piggy_bank": "\u5b58\u94b1\u7f50",
@@ -76,18 +97,18 @@
"amount": "\u91d1\u989d",
"name": "\u540d\u79f0",
"role": "\u89d2\u8272",
"iban": "\u56fd\u9645\u94f6\u884c\u5e10\u6237\u53f7\u7801 (IBAN)",
"iban": "\u56fd\u9645\u94f6\u884c\u8d26\u6237\u53f7\u7801\uff08IBAN\uff09",
"lastActivity": "\u4e0a\u6b21\u6d3b\u52a8",
"currentBalance": "\u76ee\u524d\u9980\u989d",
"balanceDiff": "\u9980\u989d\u5dee",
"next_expected_match": "\u4e0b\u4e00\u4e2a\u9884\u671f\u7684\u914d\u5bf9"
"currentBalance": "\u76ee\u524d\u4f59\u989d",
"balanceDiff": "\u4f59\u989d\u5dee",
"next_expected_match": "\u9884\u671f\u4e0b\u6b21\u652f\u4ed8"
},
"config": {
"html_language": "zh-cn"
},
"form": {
"foreign_amount": "\u5916\u5e01\u91d1\u989d",
"interest_date": "\u5229\u7387\u65e5\u671f",
"interest_date": "\u5229\u606f\u65e5\u671f",
"book_date": "\u767b\u8bb0\u65e5\u671f",
"process_date": "\u5904\u7406\u65e5\u671f",
"due_date": "\u5230\u671f\u65e5",

View File

@@ -47,6 +47,8 @@
"go_to_expenses": "Go to expenses",
"go_to_bills": "\u524d\u5f80\u60a8\u7684\u5e33\u55ae",
"bills": "\u5e33\u55ae",
"last_thirty_days": "\u6700\u8fd130\u5929",
"last_seven_days": "\u6700\u8fd17\u5929",
"go_to_piggies": "\u524d\u5f80\u60a8\u7684\u5c0f\u8c6c\u64b2\u6eff",
"saved": "Saved",
"piggy_banks": "\u5c0f\u8c6c\u64b2\u6eff",
@@ -56,6 +58,7 @@
"account_role_defaultAsset": "\u9810\u8a2d\u8cc7\u7522\u5e33\u6236",
"account_role_savingAsset": "\u5132\u84c4\u5e33\u6236",
"account_role_sharedAsset": "\u5171\u7528\u8cc7\u7522\u5e33\u6236",
"clear_location": "\u6e05\u9664\u4f4d\u7f6e",
"account_role_ccAsset": "\u4fe1\u7528\u5361",
"account_role_cashWalletAsset": "\u73fe\u91d1\u9322\u5305",
"daily_budgets": "Daily budgets",
@@ -64,6 +67,24 @@
"quarterly_budgets": "Quarterly budgets",
"half_year_budgets": "Half-yearly budgets",
"yearly_budgets": "Yearly budgets",
"split_transaction_title": "\u62c6\u5206\u4ea4\u6613\u7684\u63cf\u8ff0",
"errors_submission": "There was something wrong with your submission. Please check out the errors.",
"flash_error": "\u932f\u8aa4\uff01",
"store_transaction": "Store transaction",
"flash_success": "\u6210\u529f\uff01",
"create_another": "After storing, return here to create another one.",
"update_transaction": "Update transaction",
"after_update_create_another": "After updating, return here to continue editing.",
"reset_after": "Reset form after submission",
"bill_paid_on": "Paid on {date}",
"first_split_decides": "The first split determines the value of this field",
"first_split_overrules_source": "The first split may overrule the source account",
"first_split_overrules_destination": "The first split may overrule the destination account",
"transaction_stored_link": "<a href=\"transactions\/show\/{ID}\">Transaction #{ID} (\"{title}\")<\/a> has been stored.",
"custom_period": "Custom period",
"reset_to_current": "Reset to current period",
"select_period": "Select a period",
"location": "\u4f4d\u7f6e",
"other_budgets": "Custom timed budgets",
"journal_links": "\u4ea4\u6613\u9023\u7d50",
"go_to_withdrawals": "Go to your withdrawals",

View File

@@ -78,9 +78,9 @@ new Vue({
beforeCreate() {
this.$store.commit('initialiseStore');
this.$store.dispatch('updateCurrencyPreference');
this.$store.dispatch('dashboard/index/initialiseStore');
},
});
new Vue({
i18n,
store,

42
frontend/src/pages/transactions/edit.js vendored Normal file
View File

@@ -0,0 +1,42 @@
/*
* edit.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 store from "../../components/store";
import Edit from "../../components/transactions/Edit";
import Vue from "vue";
require('../../bootstrap');
Vue.config.productionTip = false;
// i18n
let i18n = require('../../i18n');
let props = {};
new Vue({
i18n,
store,
render(createElement) {
return createElement(Edit, {props: props});
},
beforeCreate() {
this.$store.commit('initialiseStore');
this.$store.dispatch('updateCurrencyPreference');
},
}).$mount('#transactions_edit');

19
frontend/src/scss/_variables.scss vendored Normal file
View File

@@ -0,0 +1,19 @@
/*!
* _variables.scss
* 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/>.
*/

View File

@@ -22,6 +22,7 @@ const mix = require('laravel-mix');
require('laravel-mix-bundle-analyzer');
// production
mix.webpackConfig({
resolve: {
@@ -62,6 +63,7 @@ mix
// transactions.
.js('src/pages/transactions/create.js', 'public/js/transactions')
.js('src/pages/transactions/edit.js', 'public/js/transactions')
// register page
.js('src/pages/register.js', 'public/js')
@@ -73,6 +75,7 @@ mix
// move to right dir
.copy('public/js','../public/v2/js')
.copy('fonts','../public/fonts')
.copy('images','../public/images')
.copy('public/css','../public/v2/css')
;