added vue-bootstrap
added income/expense input added Frequency enum Signed-off-by: Pascal Syma <pascal@syma.dev>
This commit is contained in:
+3
-3
@@ -2,11 +2,11 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
<title>expense</title>
|
<title>expense</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body class="py-4">
|
||||||
<div id="app"></div>
|
<div id="app" class="container"></div>
|
||||||
<!-- built files will be auto injected -->
|
<!-- built files will be auto injected -->
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Generated
+121
@@ -166,6 +166,66 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@nuxt/opencollective": {
|
||||||
|
"version": "0.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@nuxt/opencollective/-/opencollective-0.3.2.tgz",
|
||||||
|
"integrity": "sha512-XG7rUdXG9fcafu9KTDIYjJSkRO38EwjlKYIb5TQ/0WDbiTUTtUtgncMscKOYzfsY86kGs05pAuMOR+3Fi0aN3A==",
|
||||||
|
"requires": {
|
||||||
|
"chalk": "^4.1.0",
|
||||||
|
"consola": "^2.15.0",
|
||||||
|
"node-fetch": "^2.6.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-styles": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||||
|
"requires": {
|
||||||
|
"color-convert": "^2.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"chalk": {
|
||||||
|
"version": "4.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz",
|
||||||
|
"integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==",
|
||||||
|
"requires": {
|
||||||
|
"ansi-styles": "^4.1.0",
|
||||||
|
"supports-color": "^7.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"color-convert": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||||
|
"requires": {
|
||||||
|
"color-name": "~1.1.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"color-name": {
|
||||||
|
"version": "1.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||||
|
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||||
|
},
|
||||||
|
"has-flag": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
|
||||||
|
},
|
||||||
|
"supports-color": {
|
||||||
|
"version": "7.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||||
|
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||||
|
"requires": {
|
||||||
|
"has-flag": "^4.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@popperjs/core": {
|
||||||
|
"version": "2.9.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.9.2.tgz",
|
||||||
|
"integrity": "sha512-VZMYa7+fXHdwIq1TDhSXoVmSPEGM/aa+6Aiq3nVVJ9bXr24zScr+NlKFKC3iPljA7ho/GAZr+d2jOf5GIRC30Q=="
|
||||||
|
},
|
||||||
"@types/json5": {
|
"@types/json5": {
|
||||||
"version": "0.0.29",
|
"version": "0.0.29",
|
||||||
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
|
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
|
||||||
@@ -2018,6 +2078,23 @@
|
|||||||
"integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=",
|
"integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"bootstrap": {
|
||||||
|
"version": "4.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.0.tgz",
|
||||||
|
"integrity": "sha512-Io55IuQY3kydzHtbGvQya3H+KorS/M9rSNyfCGCg9WZ4pyT/lCxIlpJgG1GXW/PswzC84Tr2fBYi+7+jFVQQBw=="
|
||||||
|
},
|
||||||
|
"bootstrap-vue": {
|
||||||
|
"version": "2.21.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/bootstrap-vue/-/bootstrap-vue-2.21.2.tgz",
|
||||||
|
"integrity": "sha512-0Exe+4MZysqhZNXIKf4TzkvXaupxh9EHsoCRez0o5Dc0J7rlafayOEwql63qXv74CgZO8E4U8ugRNJko1vMvNw==",
|
||||||
|
"requires": {
|
||||||
|
"@nuxt/opencollective": "^0.3.2",
|
||||||
|
"bootstrap": ">=4.5.3 <5.0.0",
|
||||||
|
"popper.js": "^1.16.1",
|
||||||
|
"portal-vue": "^2.1.7",
|
||||||
|
"vue-functional-data-merge": "^3.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"brace-expansion": {
|
"brace-expansion": {
|
||||||
"version": "1.1.11",
|
"version": "1.1.11",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||||
@@ -2901,6 +2978,11 @@
|
|||||||
"integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==",
|
"integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"consola": {
|
||||||
|
"version": "2.15.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/consola/-/consola-2.15.3.tgz",
|
||||||
|
"integrity": "sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw=="
|
||||||
|
},
|
||||||
"console-browserify": {
|
"console-browserify": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz",
|
||||||
@@ -5319,6 +5401,15 @@
|
|||||||
"jest-regex-util": "^22.4.3"
|
"jest-regex-util": "^22.4.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"exports-loader": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/exports-loader/-/exports-loader-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-k/VFrVEUmotfkk8vZ+njG5NEXpr5Ee+BonV+AYINV2hNo3o+/UB8nEuCUQk2k6IyWIoobmXoTFO0igxrQcMV4Q==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"source-map": "^0.6.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"express": {
|
"express": {
|
||||||
"version": "4.17.1",
|
"version": "4.17.1",
|
||||||
"resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz",
|
"resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz",
|
||||||
@@ -7867,6 +7958,11 @@
|
|||||||
"merge-stream": "^1.0.1"
|
"merge-stream": "^1.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"jquery": {
|
||||||
|
"version": "3.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz",
|
||||||
|
"integrity": "sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw=="
|
||||||
|
},
|
||||||
"js-base64": {
|
"js-base64": {
|
||||||
"version": "2.6.4",
|
"version": "2.6.4",
|
||||||
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.6.4.tgz",
|
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.6.4.tgz",
|
||||||
@@ -8707,6 +8803,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node-fetch": {
|
||||||
|
"version": "2.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
|
||||||
|
"integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw=="
|
||||||
|
},
|
||||||
"node-forge": {
|
"node-forge": {
|
||||||
"version": "0.10.0",
|
"version": "0.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz",
|
||||||
@@ -9405,6 +9506,16 @@
|
|||||||
"integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==",
|
"integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"popper.js": {
|
||||||
|
"version": "1.16.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
|
||||||
|
"integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ=="
|
||||||
|
},
|
||||||
|
"portal-vue": {
|
||||||
|
"version": "2.1.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/portal-vue/-/portal-vue-2.1.7.tgz",
|
||||||
|
"integrity": "sha512-+yCno2oB3xA7irTt0EU5Ezw22L2J51uKAacE/6hMPMoO/mx3h4rXFkkBkT4GFsMDv/vEe8TNKC3ujJJ0PTwb6g=="
|
||||||
|
},
|
||||||
"portfinder": {
|
"portfinder": {
|
||||||
"version": "1.0.28",
|
"version": "1.0.28",
|
||||||
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz",
|
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz",
|
||||||
@@ -14425,6 +14536,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/vue/-/vue-2.6.12.tgz",
|
"resolved": "https://registry.npmjs.org/vue/-/vue-2.6.12.tgz",
|
||||||
"integrity": "sha512-uhmLFETqPPNyuLLbsKz6ioJ4q7AZHzD8ZVFNATNyICSZouqP2Sz0rotWQC8UNBF6VGSCs5abnKJoStA6JbCbfg=="
|
"integrity": "sha512-uhmLFETqPPNyuLLbsKz6ioJ4q7AZHzD8ZVFNATNyICSZouqP2Sz0rotWQC8UNBF6VGSCs5abnKJoStA6JbCbfg=="
|
||||||
},
|
},
|
||||||
|
"vue-enums": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue-enums/-/vue-enums-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-fCLmWRSzl6O7e9BkQ+aFdrWpvIxLlNIWswjvvCLkzsCQ4/nSCVrRd7Tr+9/651d1FGOe5v3fuPIFVP6Cjsv+0Q=="
|
||||||
|
},
|
||||||
"vue-eslint-parser": {
|
"vue-eslint-parser": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-2.0.3.tgz",
|
||||||
@@ -14456,6 +14572,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"vue-functional-data-merge": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue-functional-data-merge/-/vue-functional-data-merge-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-leT4kdJVQyeZNY1kmnS1xiUlQ9z1B/kdBFCILIjYYQDqZgLqCLa0UhjSSeRX6c3mUe6U5qYeM8LrEqkHJ1B4LA=="
|
||||||
|
},
|
||||||
"vue-hot-reload-api": {
|
"vue-hot-reload-api": {
|
||||||
"version": "2.3.4",
|
"version": "2.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz",
|
||||||
|
|||||||
+8
-1
@@ -13,7 +13,13 @@
|
|||||||
"build": "node build/build.js"
|
"build": "node build/build.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"vue": "^2.5.2"
|
"@popperjs/core": "^2.9.2",
|
||||||
|
"bootstrap": "^4.6.0",
|
||||||
|
"bootstrap-vue": "^2.21.2",
|
||||||
|
"jquery": "^3.6.0",
|
||||||
|
"popper.js": "^1.16.1",
|
||||||
|
"vue": "^2.5.2",
|
||||||
|
"vue-enums": "^1.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"autoprefixer": "^7.1.2",
|
"autoprefixer": "^7.1.2",
|
||||||
@@ -41,6 +47,7 @@
|
|||||||
"eslint-plugin-promise": "^3.4.0",
|
"eslint-plugin-promise": "^3.4.0",
|
||||||
"eslint-plugin-standard": "^3.0.1",
|
"eslint-plugin-standard": "^3.0.1",
|
||||||
"eslint-plugin-vue": "^4.0.0",
|
"eslint-plugin-vue": "^4.0.0",
|
||||||
|
"exports-loader": "^2.0.0",
|
||||||
"extract-text-webpack-plugin": "^3.0.0",
|
"extract-text-webpack-plugin": "^3.0.0",
|
||||||
"file-loader": "^1.1.4",
|
"file-loader": "^1.1.4",
|
||||||
"friendly-errors-webpack-plugin": "^1.6.1",
|
"friendly-errors-webpack-plugin": "^1.6.1",
|
||||||
|
|||||||
+81
-10
@@ -1,28 +1,99 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="app">
|
<div id="app" class="container">
|
||||||
<img src="./assets/logo.png">
|
<b-row class="results">
|
||||||
<HelloWorld/>
|
<b-col sm="12" offset-md="1" md="10">
|
||||||
|
<b-card header="Result" title="Special title treatment">
|
||||||
|
<b-card-text>With supporting text below as a natural lead-in to additional content.</b-card-text>
|
||||||
|
<p>Income: {{ calculated.yearly.in | currency }}</p>
|
||||||
|
<p>Expenses: {{ calculated.yearly.out | currency }}</p>
|
||||||
|
<p>Profit: <span :class="calculated.yearly.sum < 0 ? 'text-danger' : 'text-success'">{{ calculated.yearly.sum | currency }}</span></p>
|
||||||
|
<!-- <b-button variant="primary" size="lg" @click="calculate">Calculate</b-button>-->
|
||||||
|
</b-card>
|
||||||
|
</b-col>
|
||||||
|
</b-row>
|
||||||
|
<b-row class="inputs">
|
||||||
|
<Input title="Income" color="success" :entries="income" />
|
||||||
|
<Input title="Expenses" color="danger" :entries="expenses" />
|
||||||
|
</b-row>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import HelloWorld from './components/HelloWorld'
|
import HelloWorld from './components/HelloWorld'
|
||||||
|
import Input from './components/Input'
|
||||||
|
import Frequency from './enums/Frequency'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'App',
|
name: 'App',
|
||||||
components: {
|
components: {
|
||||||
|
Input,
|
||||||
HelloWorld
|
HelloWorld
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
income: [],
|
||||||
|
expenses: [],
|
||||||
|
calculated: {
|
||||||
|
yearly: {
|
||||||
|
in: 0,
|
||||||
|
out: 0,
|
||||||
|
sum: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
const storage = localStorage.getItem('expenses')
|
||||||
|
if (storage) {
|
||||||
|
try {
|
||||||
|
const storageObj = JSON.parse(storage)
|
||||||
|
if (storageObj.expenses && storageObj.income) {
|
||||||
|
this.$set(this, 'expenses', storageObj.expenses)
|
||||||
|
this.$set(this, 'income', storageObj.income)
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.calculate()
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
income: {
|
||||||
|
deep: true,
|
||||||
|
handler: 'calculate'
|
||||||
|
},
|
||||||
|
expenses: {
|
||||||
|
deep: true,
|
||||||
|
handler: 'calculate'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
calculate () {
|
||||||
|
const storage = JSON.stringify({expenses: this.expenses, income: this.income})
|
||||||
|
localStorage.setItem('expenses', storage)
|
||||||
|
|
||||||
|
let calc = {
|
||||||
|
in: 0,
|
||||||
|
out: 0,
|
||||||
|
sum: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
calc.in = [...this.income.map(i => Frequency.byId(i.frequency).interval * parseFloat(i.amount)), ...this.income.filter(i => i.frequency === 0).map(i => parseFloat(i.amount))].reduce((c, v) => c + v, 0)
|
||||||
|
calc.out = [...this.expenses.map(i => Frequency.byId(i.frequency).interval * parseFloat(i.amount)), ...this.expenses.filter(i => i.frequency === 0).map(i => parseFloat(i.amount))].reduce((c, v) => c + v, 0)
|
||||||
|
|
||||||
|
calc.sum = calc.in - calc.out
|
||||||
|
|
||||||
|
this.$set(this.calculated, 'yearly', calc)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
#app {
|
body {
|
||||||
font-family: 'Avenir', Helvetica, Arial, sans-serif;
|
overflow-y: scroll;
|
||||||
-webkit-font-smoothing: antialiased;
|
}
|
||||||
-moz-osx-font-smoothing: grayscale;
|
.inputs {
|
||||||
text-align: center;
|
margin-top: 20px;
|
||||||
color: #2c3e50;
|
|
||||||
margin-top: 60px;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -0,0 +1,209 @@
|
|||||||
|
<template>
|
||||||
|
<b-col md="6">
|
||||||
|
<b-card no-body>
|
||||||
|
<b-card-header :header-bg-variant="color" header-text-variant="white" class="container-fluid">
|
||||||
|
<b-row>
|
||||||
|
<b-col md="10">
|
||||||
|
<h3 class="w-75 p-3">{{ title }}</h3>
|
||||||
|
</b-col>
|
||||||
|
<b-col md="2" class="float-right align-self-center">
|
||||||
|
<b-button @click="add()" variant="primary" size="lg"><b-icon-plus /></b-button>
|
||||||
|
</b-col>
|
||||||
|
</b-row>
|
||||||
|
</b-card-header>
|
||||||
|
<b-card-body>
|
||||||
|
<b-table :fields="fields" :items="data" responsive="sm" v-model="currentItems">
|
||||||
|
<template #cell(index)="data">
|
||||||
|
{{ data.index + 1 }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #cell(options)="data">
|
||||||
|
<b-button-group size="sm">
|
||||||
|
<b-button @click="toggleDetails(data)" v-b-tooltip.hover :title="data.detailsShowing ? 'Cancel' : 'Edit'" :variant="data.detailsShowing ? 'outline-danger' : 'primary'">
|
||||||
|
<b-icon-x-square v-if="data.detailsShowing" />
|
||||||
|
<b-icon-pencil-square v-else />
|
||||||
|
</b-button>
|
||||||
|
<b-button @click="askDelete(data)" v-b-tooltip.hover title="Remove" variant="danger"><b-icon-trash /></b-button>
|
||||||
|
</b-button-group>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #cell()="data">
|
||||||
|
{{ data.value }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #cell(amount)="data">
|
||||||
|
{{ data.item.amount | currency }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #cell(frequency)="data">
|
||||||
|
{{ $enums.Frequency.byId(data.item.frequency).name }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #row-details="row">
|
||||||
|
<b-card>
|
||||||
|
<b-form @submit.prevent="save(row)">
|
||||||
|
<b-form-group
|
||||||
|
id="name-group"
|
||||||
|
label="Name:"
|
||||||
|
label-for="name">
|
||||||
|
<b-form-input
|
||||||
|
id="name"
|
||||||
|
v-model="editingCache.name"
|
||||||
|
type="text"
|
||||||
|
placeholder="Name"
|
||||||
|
required></b-form-input>
|
||||||
|
</b-form-group>
|
||||||
|
|
||||||
|
<b-form-group
|
||||||
|
id="amount-group"
|
||||||
|
label="Amount:"
|
||||||
|
label-for="amount">
|
||||||
|
<b-input-group append="€" class="mb-2 mr-sm-2 mb-sm-0">
|
||||||
|
<b-form-input
|
||||||
|
id="amount"
|
||||||
|
v-model="editingCache.amount"
|
||||||
|
type="number"
|
||||||
|
placeholder="0,00"
|
||||||
|
step="0.01"
|
||||||
|
min="0.01"
|
||||||
|
required></b-form-input>
|
||||||
|
</b-input-group>
|
||||||
|
</b-form-group>
|
||||||
|
|
||||||
|
<b-form-group
|
||||||
|
id="frequency-group"
|
||||||
|
label="Frequency:"
|
||||||
|
label-for="frequency">
|
||||||
|
<b-form-select
|
||||||
|
id="frequency"
|
||||||
|
v-model="editingCache.frequency"
|
||||||
|
type="text"
|
||||||
|
:options="$enums.Frequency.options()"
|
||||||
|
required></b-form-select>
|
||||||
|
</b-form-group>
|
||||||
|
|
||||||
|
<b-button type="submit" variant="success"><b-icon-check /> Save</b-button>
|
||||||
|
</b-form>
|
||||||
|
</b-card>
|
||||||
|
</template>
|
||||||
|
</b-table>
|
||||||
|
</b-card-body>
|
||||||
|
</b-card>
|
||||||
|
</b-col>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Vue from 'vue'
|
||||||
|
import Frequency from '../enums/Frequency'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Input',
|
||||||
|
enums: {
|
||||||
|
Frequency
|
||||||
|
},
|
||||||
|
props: [
|
||||||
|
'title',
|
||||||
|
'color',
|
||||||
|
'entries'
|
||||||
|
],
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
key: 'index',
|
||||||
|
label: '#'
|
||||||
|
},
|
||||||
|
'name',
|
||||||
|
'amount',
|
||||||
|
'frequency',
|
||||||
|
{
|
||||||
|
key: 'options',
|
||||||
|
label: ''
|
||||||
|
}
|
||||||
|
],
|
||||||
|
data: [],
|
||||||
|
currentItems: [],
|
||||||
|
editingCache: undefined,
|
||||||
|
editing: undefined
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
this.$set(this, 'data', this.$props.entries)
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
add () {
|
||||||
|
this.data.push({
|
||||||
|
name: '',
|
||||||
|
amount: 1.00,
|
||||||
|
frequency: 0
|
||||||
|
})
|
||||||
|
|
||||||
|
this.toggleDetails({index: this.data.length - 1, toggleDetails: () => {}})
|
||||||
|
},
|
||||||
|
askDelete (row) {
|
||||||
|
// ask before deletion
|
||||||
|
this.$bvModal.msgBoxConfirm(`Please confirm that you want to delete '${row.item.name}'?`, {
|
||||||
|
title: 'Are you sure?',
|
||||||
|
size: 'sm',
|
||||||
|
buttonSize: 'sm',
|
||||||
|
okVariant: 'danger',
|
||||||
|
okTitle: 'Yes',
|
||||||
|
cancelTitle: 'No',
|
||||||
|
footerClass: 'p-2',
|
||||||
|
hideHeaderClose: false,
|
||||||
|
centered: true
|
||||||
|
})
|
||||||
|
.then(confirmedDelete => {
|
||||||
|
if (!confirmedDelete) return
|
||||||
|
this.data.splice(row.index, 1)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
save (row) {
|
||||||
|
// override active data with from cache
|
||||||
|
this.$set(this.data, row.index, this.editingCache)
|
||||||
|
this.toggleDetails(row)
|
||||||
|
},
|
||||||
|
toggleDetails (row) {
|
||||||
|
// close all open details
|
||||||
|
this.currentItems.forEach((item, i) => {
|
||||||
|
if (i === row.index) return
|
||||||
|
this.$set(item, '_showDetails', false)
|
||||||
|
})
|
||||||
|
|
||||||
|
// create (or reset) local copy for form data
|
||||||
|
if (row.detailsShowing) {
|
||||||
|
this.editingCache = undefined
|
||||||
|
} else {
|
||||||
|
this.editingCache = Vue.util.extend({}, row.item)
|
||||||
|
}
|
||||||
|
|
||||||
|
row.toggleDetails()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
data: {
|
||||||
|
deep: true,
|
||||||
|
handler () {
|
||||||
|
this.$emit('input', this.data)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
entries: {
|
||||||
|
deep: true,
|
||||||
|
handler () {
|
||||||
|
this.$set(this, 'data', this.$props.entries)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* highlight selected row */
|
||||||
|
.b-table-has-details {
|
||||||
|
background: var(--dark);
|
||||||
|
color: var(--white);
|
||||||
|
}
|
||||||
|
.b-table-has-details + tr {
|
||||||
|
background: var(--dark);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
class Frequency {
|
||||||
|
constructor (name, interval) {
|
||||||
|
this.name = name
|
||||||
|
this.interval = interval
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Frequency.ONCE = new Frequency('Once', 0)
|
||||||
|
Frequency.YEARLY = new Frequency('Yearly', 1)
|
||||||
|
Frequency.BIMONTHLY = new Frequency('Bi-Monthly', 6)
|
||||||
|
Frequency.MONTHLY = new Frequency('Monthly', 12)
|
||||||
|
Frequency.BIWEEKLY = new Frequency('Bi-Weekly', 26)
|
||||||
|
Frequency.WEEKLY = new Frequency('Weekly', 52)
|
||||||
|
Frequency.DAILY = new Frequency('Daily', 52 * 7)
|
||||||
|
|
||||||
|
Frequency.byId = id => {
|
||||||
|
try { return Frequency[Object.keys(Frequency)[id]] } catch (e) { return Frequency.ONCE }
|
||||||
|
}
|
||||||
|
|
||||||
|
Frequency.values = () => {
|
||||||
|
let keys = Object.keys(Frequency)
|
||||||
|
keys.splice(-3, 3)
|
||||||
|
|
||||||
|
return keys.map(k => Frequency[k])
|
||||||
|
}
|
||||||
|
|
||||||
|
Frequency.options = () => {
|
||||||
|
return Frequency.values().map((f, i) => {
|
||||||
|
return {
|
||||||
|
value: i,
|
||||||
|
text: f.name
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Frequency
|
||||||
+25
@@ -1,7 +1,32 @@
|
|||||||
// The Vue build version to load with the `import` command
|
// The Vue build version to load with the `import` command
|
||||||
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
|
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
|
||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
|
import enums from 'vue-enums'
|
||||||
import App from './App'
|
import App from './App'
|
||||||
|
import { BootstrapVue, IconsPlugin } from 'bootstrap-vue'
|
||||||
|
|
||||||
|
import 'bootstrap/dist/js/bootstrap'
|
||||||
|
import 'bootstrap/dist/css/bootstrap.min.css'
|
||||||
|
import 'bootstrap-vue/dist/bootstrap-vue.css'
|
||||||
|
|
||||||
|
Vue.use(enums, {namespace: '$enums'})
|
||||||
|
|
||||||
|
// Make BootstrapVue available throughout your project
|
||||||
|
Vue.use(BootstrapVue)
|
||||||
|
// Optionally install the BootstrapVue icon components plugin
|
||||||
|
Vue.use(IconsPlugin)
|
||||||
|
|
||||||
|
Vue.filter('currency', function (value) {
|
||||||
|
if (typeof value !== 'number') {
|
||||||
|
try {
|
||||||
|
value = parseFloat(value)
|
||||||
|
} catch (e) {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${value.toFixed(2)} €`
|
||||||
|
})
|
||||||
|
|
||||||
Vue.config.productionTip = false
|
Vue.config.productionTip = false
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user