new Project with Quasar

This commit is contained in:
Tim Gröger 2020-10-02 09:13:14 +02:00
parent f23be34a77
commit 4f64933555
124 changed files with 8666 additions and 18995 deletions

9
.editorconfig Normal file
View File

@ -0,0 +1,9 @@
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

7
.eslintignore Normal file
View File

@ -0,0 +1,7 @@
/dist
/src-bex/www
/src-capacitor
/src-cordova
/.quasar
/node_modules
/src-ssr

86
.eslintrc.js Normal file
View File

@ -0,0 +1,86 @@
const { resolve } = require('path');
module.exports = {
// https://eslint.org/docs/user-guide/configuring#configuration-cascading-and-hierarchy
// This option interrupts the configuration hierarchy at this file
// Remove this if you have an higher level ESLint config file (it usually happens into a monorepos)
root: true,
// https://eslint.vuejs.org/user-guide/#how-to-use-custom-parser
// Must use parserOptions instead of "parser" to allow vue-eslint-parser to keep working
// `parser: 'vue-eslint-parser'` is already included with any 'plugin:vue/**' config and should be omitted
parserOptions: {
// https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/parser#configuration
// https://github.com/TypeStrong/fork-ts-checker-webpack-plugin#eslint
// Needed to make the parser take into account 'vue' files
extraFileExtensions: ['.vue'],
parser: '@typescript-eslint/parser',
project: resolve(__dirname, './tsconfig.json'),
tsconfigRootDir: __dirname,
ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features
sourceType: 'module' // Allows for the use of imports
},
env: {
browser: true
},
// Rules order is important, please avoid shuffling them
extends: [
// Base ESLint recommended rules
// 'eslint:recommended',
// https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin#usage
// ESLint typescript rules
'plugin:@typescript-eslint/recommended',
// consider disabling this class of rules if linting takes too long
'plugin:@typescript-eslint/recommended-requiring-type-checking',
// Uncomment any of the lines below to choose desired strictness,
// but leave only one uncommented!
// See https://eslint.vuejs.org/rules/#available-rules
'plugin:vue/essential', // Priority A: Essential (Error Prevention)
// 'plugin:vue/strongly-recommended', // Priority B: Strongly Recommended (Improving Readability)
// 'plugin:vue/recommended', // Priority C: Recommended (Minimizing Arbitrary Choices and Cognitive Overhead)
// https://github.com/prettier/eslint-config-prettier#installation
// usage with Prettier, provided by 'eslint-config-prettier'.
'prettier',
'prettier/@typescript-eslint',
'prettier/vue'
],
plugins: [
// required to apply rules which need type information
'@typescript-eslint',
// https://eslint.vuejs.org/user-guide/#why-doesn-t-it-work-on-vue-file
// required to lint *.vue files
'vue',
// https://github.com/typescript-eslint/typescript-eslint/issues/389#issuecomment-509292674
// Prettier has not been included as plugin to avoid performance impact
// add it as an extension for your IDE
],
globals: {
ga: true, // Google Analytics
cordova: true,
__statics: true,
process: true,
Capacitor: true,
chrome: true
},
// add your custom rules here
rules: {
'prefer-promise-reject-errors': 'off',
// TypeScript
quotes: ['warn', 'single', { avoidEscape: true }],
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
// allow debugger during development only
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
}
}

22
.gitignore vendored
View File

@ -1,10 +1,24 @@
.DS_Store
.thumbs.db
node_modules
# Quasar core related directories
.quasar
/dist
# local env files
.env.local
.env.*.local
# Cordova related directories and files
/src-cordova/node_modules
/src-cordova/platforms
/src-cordova/plugins
/src-cordova/www
# Capacitor related directories and files
/src-capacitor/www
/src-capacitor/node_modules
# BEX related directories and files
/src-bex/www
/src-bex/js/core
# Log files
npm-debug.log*
@ -13,9 +27,7 @@ yarn-error.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

8
.postcssrc.js Normal file
View File

@ -0,0 +1,8 @@
// https://github.com/michael-ciniawsky/postcss-load-config
module.exports = {
plugins: [
// to edit target browsers: use "browserslist" field in package.json
require('autoprefixer')
]
}

4
.prettierrc Normal file
View File

@ -0,0 +1,4 @@
{
"singleQuote": true,
"semi": true
}

12
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,12 @@
{
"recommendations": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"octref.vetur"
],
"unwantedRecommendations": [
"hookyqr.beautify",
"dbaeumer.jshint",
"ms-vscode.vscode-typescript-tslint-plugin"
]
}

7
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,7 @@
{
"vetur.validation.template": false,
"vetur.format.enable": false,
"eslint.validate": ["javascript", "javascriptreact", "typescript", "vue"],
"typescript.tsdk": "node_modules/typescript/lib",
"vetur.experimental.templateInterpolationService": true
}

View File

@ -1,24 +1,26 @@
# newgruecht-vue
# Flaschengeist (flaschengeist-frontend)
## Project setup
```
Dynamischen Managementsystem für Studentenclubs
## Install the dependencies
```bash
npm install
```
### Compiles and hot-reloads for development
```
npm run serve
### Start the app in development mode (hot-code reloading, error reporting, etc.)
```bash
quasar dev
```
### Compiles and minifies for production
```
npm run build
```
### Lints and fixes files
```
### Lint the files
```bash
npm run lint
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).
### Build the app for production
```bash
quasar build
```
### Customize the configuration
See [Configuring quasar.conf.js](https://quasar.dev/quasar-cli/quasar-conf-js).

View File

@ -1,5 +1,6 @@
/* eslint-env node */
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
'@quasar/babel-preset-app'
]
}

View File

@ -1,21 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDazCCAlOgAwIBAgIJAJGH2ozWvd1RMA0GCSqGSIb3DQEBCwUAMFMxCzAJBgNV
BAYTAkRFMQ8wDQYDVQQIDAZTYXhvbnkxEDAOBgNVBAcMB0RyZXNkZW4xITAfBgNV
BAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMDAxMTcwOTA0MDFaFw0z
MDAxMDQwOTA0MDFaMEQxCzAJBgNVBAYTAkRFMQ8wDQYDVQQIDAZTYXhvbnkxEDAO
BgNVBAcMB0RyZXNkZW4xEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBALlkr1UOQypLKicESRnse52d5mAX9MjZQpH0/Y5u
V5WxpPSasmOpt4MRj5MWTfTK2ukj/jLtPAMsggUh7wMXb1uytHj7T5mtiahXBM0H
1sUi2nScXR6doQZlmqKWDGrVS7WHULM01WhirsnxI8S8e6Evpk4F5/RafKA8FgYI
Ongg6S1B16+7T0e/FnILoMjKr1jpgzXnVkPFIneu/qVevSNco5/aw+bc6sjeS/ZA
65dXFGpDlw0lPRHLT5/CgNyMyiLYov7KwMycZw7uxa1ynO+73tqe5tvO/DiMpAPJ
EkrSz/StYBsGJxDhwq5RT31tHVtHhTf0rk1BmaoQJ0Aq7iECAwEAAaNRME8wHwYD
VR0jBBgwFoAUt8P5gBfN9hCUAiWhtPH5fTWnctAwCQYDVR0TBAIwADALBgNVHQ8E
BAMCBPAwFAYDVR0RBA0wC4IJbG9jYWxob3N0MA0GCSqGSIb3DQEBCwUAA4IBAQCD
fBByVq8AbV1DMrY+MElb/nZA5/cuGnUpBpjSlk5OnYHWtywuQk6veiiJ0S2fNfqf
RzwOFuZDHKmIcH0574VssLfUynMKP3w3xb2ZNic3AxAdhzZ6LXLx6+qF5tYcL7oC
UWmj5Mo9SkX5HZLEGamQlVyGOGKNatxep4liyoSeKXr0AOHYfB4AkDhVZn7yQc/v
But42fLBg4mE+rk4UBYOHA4XdoFwqgTCNZq2RxKzvG9LIcok6lOc6gDnfTsH8GqE
byGpfIIQAXF8aftCm4dGXxtzMh8C5d0t2Ell9g+Rr8i/enebT2nJ9B9ptldDjhcZ
7I0ywGsXwrh0EwFsX74/
-----END CERTIFICATE-----

View File

@ -1,28 +0,0 @@
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC5ZK9VDkMqSyon
BEkZ7HudneZgF/TI2UKR9P2ObleVsaT0mrJjqbeDEY+TFk30ytrpI/4y7TwDLIIF
Ie8DF29bsrR4+0+ZrYmoVwTNB9bFItp0nF0enaEGZZqilgxq1Uu1h1CzNNVoYq7J
8SPEvHuhL6ZOBef0WnygPBYGCDp4IOktQdevu09HvxZyC6DIyq9Y6YM151ZDxSJ3
rv6lXr0jXKOf2sPm3OrI3kv2QOuXVxRqQ5cNJT0Ry0+fwoDcjMoi2KL+ysDMnGcO
7sWtcpzvu97anubbzvw4jKQDyRJK0s/0rWAbBicQ4cKuUU99bR1bR4U39K5NQZmq
ECdAKu4hAgMBAAECggEABoMQ3Y34sf2d52zxHGYAGZM4SlvND1kCS5otZdleXjW1
M5pTdci6V3JAdswrxNNzSQkonqVSnFHt5zw/5v3lvXTTfgRl0WIVGcKkuobx9k65
Gat8YdzrkQv0mI1otj/zvtaX8ROEA3yj4xgDR5/PP+QqlUcD1MNw6TfzFhcn5pxB
/RDPmvarMhzMdDW60Uub6Z7e/kVPuXWrW4bDyULd1d1NoSibnFZi+vGY0Lc1ctDW
2Vl7A8RFTcQi6Cjx/FwgPGJTBE4UMjIBO3wnoPQBMrsSxeGhcarerqIlEafgT4XN
p9BMtRyaXE7TTb1BXc35ZYNJLDLJKQxABhrEHtFreQKBgQDpiGwuKAFK8BLPlbAx
zkShhKd9fhlwm2bfRv3cojPQZsxn0BjefmtrISbKCD79Ivyn7TnOyYAoKAxdp2q9
wtz94aAXV2lfhUw2lhcb/aw4sXuY/s1XnVyoglOO8pYRCUN0o80pKuWFsaDyy/uL
LhINff1oMNCa7vmMdu8Ccz0o/wKBgQDLOqdTQhSFs4f1yhlDDH3pqT6eKvtFNeRJ
usxYDnAyRXHRqwhQ86z1nBZIgwXqq7PfO9V5Y/l6/2HmmA2ufjS8aBTNpCUMuvJk
y98Z4hTjKRdnVlMUjHq9ahCixJVQ8pcCnWRFdeAwSKhHQiJEFLYeYOIrUeCIYJI4
FiCshSPI3wKBgGU0ErWZ7p18FprRIs8itYlNhIwUxo+POPCPwloIDO5GblSa0Pwy
yvhdIIMzOaDXtahMXN3pYtmEKX+4msBrnvuC+K7E2cxkZtfNCWy+7RCQkaCG45QR
hOMdv3pWVIRDgHEevz0U8uySQs6VaYgySe6A5/1sEiriX1DpBcEJEbsfAoGAKUCb
rGvSbJ1XsM24OQL1IBQJsON6o77fuxOe3RT5M0sjYnL8OipsZmKrp0ZpUgxOc7ba
i0x+3LewMLWWuV/G5qOd7WwvVRkxkMJNZByfLskthf1g2d/2HjLEc7XBtW+4tYAr
VWoq+sIU3noPKJCnsxzpa++vyx8HLzlWoo5YCDMCgYBJvGH2zMgInlQNO/2XY5nl
E53EZMex+RDq8Wzr4tRM3IrCGc2t8WKEQ/9teKNH0tg9xib0vhqqmiGl1xNfqJVo
ePJyfgFabeUx9goG3mgTdV9woSRlBJso62dM0DAC/jsJoHnVzgokysR4/BfW9Da+
AYTxRZSNbfmsTHawXqG8Fw==
-----END PRIVATE KEY-----

12282
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,61 +1,46 @@
{
"name": "newgruecht-vue",
"version": "1.1.0",
"name": "flaschengeist-frontend",
"version": "0.0.1",
"description": "Dynamischen Managementsystem für Studentenclubs",
"productName": "Flaschengeist",
"author": "Tim Gröger <tim@groeger-clan.de>",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
"lint": "eslint --ext .js,.ts,.vue ./",
"test": "echo \"No test specified\" && exit 0"
},
"dependencies": {
"@mdi/font": "^4.9.95",
"@mdi/js": "^4.9.95",
"@quasar/extras": "^1.0.0",
"@vue/composition-api": "^0.6.4",
"axios": "^0.18.1",
"core-js": "^3.6.5",
"vue": "^2.6.10",
"vue-router": "^3.2.0",
"vuetify": "^2.3.8",
"vuex": "^3.4.0"
"quasar": "^1.0.0"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^4.3.1",
"@vue/cli-plugin-eslint": "^4.3.1",
"@vue/cli-plugin-router": "^4.3.1",
"@vue/cli-plugin-vuex": "^4.3.1",
"@vue/cli-service": "^4.3.1",
"@vue/eslint-config-prettier": "^6.0.0",
"axios": "^0.19.2",
"babel-eslint": "^10.1.0",
"eslint": "^5.16.0",
"eslint-plugin-prettier": "^3.1.3",
"eslint-plugin-vue": "^5.0.0",
"material-design-icons-iconfont": "^5.0.1",
"prettier": "^1.19.1",
"sass": "^1.26.5",
"sass-loader": "^8.0.2",
"vue-cli-plugin-vuetify": "^2.0.5",
"vue-template-compiler": "^2.6.10",
"vuetify-loader": "^1.4.4"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"plugin:prettier/recommended",
"@vue/prettier",
"eslint:recommended"
],
"rules": {
"no-console": "off"
},
"parserOptions": {
"parser": "babel-eslint"
}
"@quasar/app": "^2.0.0",
"@types/node": "^10.17.15",
"@typescript-eslint/eslint-plugin": "^3.3.0",
"@typescript-eslint/parser": "^3.3.0",
"babel-eslint": "^10.0.1",
"eslint": "^6.8.0",
"eslint-config-prettier": "^6.9.0",
"eslint-loader": "^3.0.3",
"eslint-plugin-vue": "^6.1.2"
},
"browserslist": [
"> 1%",
"last 2 versions"
]
"last 10 Chrome versions",
"last 10 Firefox versions",
"last 4 Edge versions",
"last 7 Safari versions",
"last 8 Android versions",
"last 8 ChromeAndroid versions",
"last 8 FirefoxAndroid versions",
"last 10 iOS versions",
"last 5 Opera versions"
],
"engines": {
"node": ">= 10.18.1",
"npm": ">= 6.13.4",
"yarn": ">= 1.21.1"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 555 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 998 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -1,19 +0,0 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>wuicon.ico">
<title>Flaschengeist</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@latest/css/materialdesignicons.min.css">
</head>
<body>
<noscript>
<strong>We're sorry but newgruecht-vue doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

207
quasar.conf.js Normal file
View File

@ -0,0 +1,207 @@
/*
* This file runs in a Node context (it's NOT transpiled by Babel), so use only
* the ES6 features that are supported by your Node version. https://node.green/
*/
// Configuration for your app
// https://quasar.dev/quasar-cli/quasar-conf-js
/* eslint-env node */
/* eslint-disable @typescript-eslint/no-var-requires */
const { configure } = require('quasar/wrappers');
module.exports = configure(function (ctx) {
return {
// https://quasar.dev/quasar-cli/supporting-ts
supportTS: {
tsCheckerConfig: {
eslint: true
}
},
// https://quasar.dev/quasar-cli/prefetch-feature
// preFetch: true,
// app boot file (/src/boot)
// --> boot files are part of "main.js"
// https://quasar.dev/quasar-cli/boot-files
boot: [
'composition-api',
'axios',
],
// https://quasar.dev/quasar-cli/quasar-conf-js#Property%3A-css
css: [
'app.scss'
],
// https://github.com/quasarframework/quasar/tree/dev/extras
extras: [
// 'ionicons-v4',
// 'mdi-v5',
// 'fontawesome-v5',
// 'eva-icons',
// 'themify',
// 'line-awesome',
// 'roboto-font-latin-ext', // this or either 'roboto-font', NEVER both!
'roboto-font', // optional, you are not bound to it
'material-icons', // optional, you are not bound to it
],
// Full list of options: https://quasar.dev/quasar-cli/quasar-conf-js#Property%3A-build
build: {
vueRouterMode: 'hash', // available values: 'hash', 'history'
// transpile: false,
// Add dependencies for transpiling with Babel (Array of string/regex)
// (from node_modules, which are by default not transpiled).
// Applies only if "transpile" is set to true.
// transpileDependencies: [],
// rtl: false, // https://quasar.dev/options/rtl-support
// preloadChunks: true,
// showProgress: false,
// gzip: true,
// analyze: true,
// Options below are automatically set depending on the env, set them if you want to override
// extractCSS: false,
// https://quasar.dev/quasar-cli/handling-webpack
extendWebpack (cfg) {
// linting is slow in TS projects, we execute it only for production builds
if (ctx.prod) {
cfg.module.rules.push({
enforce: 'pre',
test: /\.(js|vue)$/,
loader: 'eslint-loader',
exclude: /node_modules/
})
}
},
},
// Full list of options: https://quasar.dev/quasar-cli/quasar-conf-js#Property%3A-devServer
devServer: {
https: false,
port: 8080,
open: true // opens browser window automatically
},
// https://quasar.dev/quasar-cli/quasar-conf-js#Property%3A-framework
framework: {
iconSet: 'material-icons', // Quasar icon set
lang: 'en-us', // Quasar language pack
config: {},
// Possible values for "importStrategy":
// * 'auto' - (DEFAULT) Auto-import needed Quasar components & directives
// * 'all' - Manually specify what to import
importStrategy: 'auto',
// For special cases outside of where "auto" importStrategy can have an impact
// (like functional components as one of the examples),
// you can manually specify Quasar components/directives to be available everywhere:
//
// components: [],
// directives: [],
// Quasar plugins
plugins: []
},
// animations: 'all', // --- includes all animations
// https://quasar.dev/options/animations
animations: [],
// https://quasar.dev/quasar-cli/developing-ssr/configuring-ssr
ssr: {
pwa: false
},
// https://quasar.dev/quasar-cli/developing-pwa/configuring-pwa
pwa: {
workboxPluginMode: 'GenerateSW', // 'GenerateSW' or 'InjectManifest'
workboxOptions: {}, // only for GenerateSW
manifest: {
name: 'Flaschengeist',
short_name: 'Flaschengeist',
description: 'Dynamischen Managementsystem für Studentenclubs',
display: 'standalone',
orientation: 'portrait',
background_color: '#ffffff',
theme_color: '#027be3',
icons: [
{
src: 'icons/icon-128x128.png',
sizes: '128x128',
type: 'image/png'
},
{
src: 'icons/icon-192x192.png',
sizes: '192x192',
type: 'image/png'
},
{
src: 'icons/icon-256x256.png',
sizes: '256x256',
type: 'image/png'
},
{
src: 'icons/icon-384x384.png',
sizes: '384x384',
type: 'image/png'
},
{
src: 'icons/icon-512x512.png',
sizes: '512x512',
type: 'image/png'
}
]
}
},
// Full list of options: https://quasar.dev/quasar-cli/developing-cordova-apps/configuring-cordova
cordova: {
// noIosLegacyBuildFlag: true, // uncomment only if you know what you are doing
},
// Full list of options: https://quasar.dev/quasar-cli/developing-capacitor-apps/configuring-capacitor
capacitor: {
hideSplashscreen: true
},
// Full list of options: https://quasar.dev/quasar-cli/developing-electron-apps/configuring-electron
electron: {
bundler: 'packager', // 'packager' or 'builder'
packager: {
// https://github.com/electron-userland/electron-packager/blob/master/docs/api.md#options
// OS X / Mac App Store
// appBundleId: '',
// appCategoryType: '',
// osxSign: '',
// protocol: 'myapp://path',
// Windows only
// win32metadata: { ... }
},
builder: {
// https://www.electron.build/configuration/configuration
appId: 'flaschengeist-frontend'
},
// More info: https://quasar.dev/quasar-cli/developing-electron-apps/node-integration
nodeIntegration: true,
extendWebpack (/* cfg */) {
// do something with Electron main process Webpack cfg
// chainWebpack also available besides this extendWebpack
}
}
}
});

View File

@ -1,232 +1,12 @@
<template>
<v-app>
<TitleBar />
<div id="q-app">
<router-view />
<v-footer app>
<span class="px-4 d-none d-sm-flex"
>&copy; {{ new Date().getFullYear() }}
<v-btn x-small text class="text-none subtitle-1" href="https://wu5.de">
Studentenclub Wu5 e.V.
</v-btn>
</span>
<span>
<v-btn text x-small href="https://wu5.de/impressum">
Impressum
</v-btn>
<v-btn text x-small href="https://wu5.de/datenschutz">
Datenschutzerklärung
</v-btn>
<v-btn
text
x-small
v-if="isLoggedIn"
href="https://groeger-clan.duckdns.org/redmine/projects/geruecht/issues/new"
>
Bugs?
</v-btn>
</span>
<v-spacer />
<div v-if="isLoggedIn && !change" :key="render">
<v-hover
v-slot:default="{ hover }"
open-delay="200"
close-delay="200"
class="d-none d-sm-flex"
>
<v-sheet
:elevation="hover ? 16 : 0"
color="#f5f5f5"
@click="change = !change"
>{{ calcTime }}</v-sheet
>
</v-hover>
<v-hover
v-slot:default="{ hover }"
open-delay="200"
close-delay="200"
class="d-flex d-sm-none"
>
<v-sheet
:elevation="hover ? 16 : 0"
color="#f5f5f5"
@click="change = !change"
>{{ calcTimeLittle }}</v-sheet
>
</v-hover>
</div>
<v-dialog v-model="change" max-width="300">
<v-card>
<v-card-title>
Zeit bis zum Logout ändern
</v-card-title>
<v-card-text>
<v-combobox
solo
:items="lifeTimes"
item-text="text"
item-value="value"
v-model="selectLifetime"
return-object
/>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn text @click="change = false">Abbrechen</v-btn>
<v-btn color="primary" text @click="save()">Speichern</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-footer>
</v-app>
</div>
</template>
<script lang="ts">
import { defineComponent } from '@vue/composition-api';
<script>
import TitleBar from './components/TitleBar'
import { mapGetters, mapActions } from 'vuex'
export default {
export default defineComponent({
name: 'App',
components: { TitleBar },
data: () => ({
render: 0,
timer: null,
change: false,
selectLifetime: { text: '30 Minuten', value: 1800 },
lifeTimes: [
{
text: '5 Minuten',
value: 300
},
{
text: '10 Minuten',
value: 600
},
{
text: '15 Minuten',
value: 900
},
{
text: '30 Minuten',
value: 1800
},
{
text: '1 Stunde',
value: 3600
},
{
text: '2 Stunden',
value: 7200
},
{
text: '3 Stunden',
value: 10800
},
{
text: '1 Tag',
value: 86400
},
{
text: '2 Tage',
value: 172800
},
{
text: '1 Woche',
value: 604800
},
{
text: '1 Monat',
value: 2678400
}
]
}),
created() {
if (this.isLoggedIn) {
this.getLifetime()
}
this.timer = setInterval(this.test, 1000)
},
methods: {
...mapActions(['setLifetime', 'saveLifetime', 'logout', 'getLifetime']),
test() {
if (this.isLoggedIn) {
if (this.lifeTime == 0) this.logout()
this.setLifetime(this.lifeTime - 1)
}
},
save() {
this.saveLifetime(this.selectLifetime.value)
this.change = false
}
},
computed: {
...mapGetters(['isLoggedIn', 'lifeTime', 'getMinute', 'getSeconds']),
calcTime() {
var minutes = this.lifeTime / 60
var seconds = this.lifeTime % 60
var minutesString =
minutes < 10 ? '0' + Math.floor(minutes % 60) : Math.floor(minutes % 60)
var secondsString =
seconds < 10 ? '0' + Math.floor(seconds) : Math.floor(seconds)
if (minutes > 60) {
var hours = minutes / 60
if (hours > 24) {
var days = hours / 24
var now = new Date()
var dayMonth = new Date(
now.getFullYear(),
now.getMonth() + 1,
0
).getDate()
if (days >= dayMonth) {
return Math.floor(days / dayMonth) + ' Monate bis zum Logout'
} else {
return Math.floor(days) + ' Tage bis zum Logout'
}
} else {
return (
Math.floor(hours) +
':' +
minutesString +
':' +
secondsString +
' Stunden bis zum Logout'
)
}
} else {
return minutesString + ':' + secondsString + ' Minuten bis zum Logout'
}
},
calcTimeLittle() {
var minutes = this.lifeTime / 60
var seconds = this.lifeTime % 60
var minutesString =
minutes < 10 ? '0' + Math.floor(minutes % 60) : Math.floor(minutes % 60)
var secondsString =
seconds < 10 ? '0' + Math.floor(seconds) : Math.floor(seconds)
if (minutes > 60) {
var hours = minutes / 60
if (hours > 24) {
var days = hours / 24
var now = new Date()
var dayMonth = new Date(
now.getFullYear(),
now.getMonth() + 1,
0
).getDate()
if (days >= dayMonth) {
return Math.floor(days / dayMonth) + 'M'
} else {
return Math.floor(days) + 'D'
}
} else {
return Math.floor(hours) + ':' + minutesString + ':' + secondsString
}
} else {
return minutesString + ':' + secondsString
}
}
},
beforeDestroy() {
clearInterval(this.timer)
}
}
});
</script>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 322 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

View File

@ -1 +0,0 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 87.5 100"><defs><style>.cls-1{fill:#1697f6;}.cls-2{fill:#7bc6ff;}.cls-3{fill:#1867c0;}.cls-4{fill:#aeddff;}</style></defs><title>Artboard 46</title><polyline class="cls-1" points="43.75 0 23.31 0 43.75 48.32"/><polygon class="cls-2" points="43.75 62.5 43.75 100 0 14.58 22.92 14.58 43.75 62.5"/><polyline class="cls-3" points="43.75 0 64.19 0 43.75 48.32"/><polygon class="cls-4" points="64.58 14.58 87.5 14.58 43.75 100 43.75 62.5 64.58 14.58"/></svg>

Before

Width:  |  Height:  |  Size: 539 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -0,0 +1,191 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="67.407623mm"
height="62.908276mm"
viewBox="0 0 238.84591 222.90334"
id="svg3570"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="quasar-logo-full.svg">
<defs
id="defs3572" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1.979899"
inkscape:cx="-39.753589"
inkscape:cy="27.706388"
inkscape:document-units="px"
inkscape:current-layer="g4895-4-4"
showgrid="false"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:window-width="1920"
inkscape:window-height="1056"
inkscape:window-x="0"
inkscape:window-y="24"
inkscape:window-maximized="1" />
<metadata
id="metadata3575">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-277.71988,-312.33911)">
<g
id="g4895-4-4"
transform="translate(1419.0442,398.9018)">
<g
transform="translate(-29.620665,-4)"
id="g4579-2-20">
<g
id="g4445-2-0"
transform="translate(12.499948,7.809312)">
<g
inkscape:export-ydpi="44.860481"
inkscape:export-xdpi="44.860481"
inkscape:export-filename="/home/emanuele/Desktop/logo1.png"
transform="translate(-712.85583,-503.26814)"
id="g4561-6-7-0">
<g
transform="translate(16.233481,0)"
style="font-style:normal;font-weight:normal;font-size:50.25774765px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#263238;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="flowRoot4513-6-6-08">
<path
d="m -402.73125,631.46823 q -0.6125,0.0438 -1.3125,0.0875 -0.65625,0 -1.4,0 l -9.31875,0 q -12.81875,0 -12.81875,-8.44375 l 0,-13.475 q 0,-8.26875 12.6,-8.26875 l 9.75625,0 q 12.6,0 12.6,8.26875 l 0,13.475 q 0,5.03125 -4.4625,7.04375 l 3.10625,2.14375 q 1.35625,0.83125 1.35625,1.70625 0,0.875 -0.7,1.3125 -0.65625,0.48125 -1.88125,0.48125 -0.30625,0 -0.7875,-0.13125 -0.4375,-0.0875 -1.05,-0.48125 l -5.6875,-3.71875 z m 5.38125,-21.74375 q 0,-4.76875 -7.9625,-4.76875 l -9.58125,0 q -7.9625,0 -7.9625,4.76875 l 0,13.3875 q 0,4.94375 8.3125,4.94375 l 8.88125,0 q 8.3125,0 8.3125,-4.94375 l 0,-13.3875 z"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:43.75px;font-family:'Neuropol X';-inkscape-font-specification:'Neuropol X';text-align:start;letter-spacing:5px;word-spacing:0px;text-anchor:start;fill:#263238;fill-opacity:1"
id="path3428" />
<path
d="m -368.0585,631.64323 q -11.2875,0 -11.2875,-6.9125 l 0,-12.73125 q 0,-1.8375 2.31875,-1.8375 2.31875,0 2.31875,1.8375 l 0,12.775 q 0,3.325 6.475,3.325 l 8.3125,0 q 6.475,0 6.475,-3.325 l 0,-12.775 q 0,-1.8375 2.31875,-1.8375 2.3625,0 2.3625,1.8375 l 0,12.73125 q 0,6.9125 -11.2875,6.9125 l -8.00625,0 z"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:43.75px;font-family:'Neuropol X';-inkscape-font-specification:'Neuropol X';text-align:start;letter-spacing:5px;word-spacing:0px;text-anchor:start;fill:#263238;fill-opacity:1"
id="path3430" />
<path
d="m -327.2833,631.64323 q -9.3625,0 -9.3625,-5.81875 l 0,-2.49375 q 0,-5.775 9.3625,-5.775 l 18.59375,0 0,-0.65625 q 0,-3.0625 -5.38125,-3.0625 l -6.16875,0 q -2.1875,0 -2.1875,-1.70625 0,-1.75 2.1875,-1.75 l 6.16875,0 q 9.93125,0 9.93125,6.51875 l 0,8.575 q 0,6.16875 -9.5375,6.16875 l -13.60625,0 z m 13.34375,-3.4125 q 5.25,0 5.25,-2.8875 l 0,-4.76875 -18.24375,0 q -5.11875,0 -5.11875,2.66875 l 0,2.275 q 0,2.7125 5.11875,2.7125 l 12.99375,0 z"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:43.75px;font-family:'Neuropol X';-inkscape-font-specification:'Neuropol X';text-align:start;letter-spacing:5px;word-spacing:0px;text-anchor:start;fill:#263238;fill-opacity:1"
id="path3432" />
<path
d="m -262.77031,626.74323 q 0,4.9 -9.975,4.9 l -17.0625,0 q -2.1875,0 -2.1875,-1.70625 0,-1.70625 2.1875,-1.70625 l 17.0625,0 q 5.38125,0 5.38125,-1.4875 l 0,-2.45 q 0,-1.4875 -5.38125,-1.4875 l -9.0125,0 q -9.975,0 -9.975,-4.76875 l 0,-2.05625 q 0,-5.6 10.28125,-5.6 l 5.99375,0 q 2.23125,0 2.23125,1.75 0,0.875 -0.6125,1.3125 -0.56875,0.39375 -1.61875,0.39375 l -5.99375,0 q -5.73125,0 -5.73125,2.14375 l 0,1.925 q 0,1.79375 5.6875,1.79375 l 9.0125,0 q 9.7125,0 9.7125,4.4625 l 0,2.58125 z"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:43.75px;font-family:'Neuropol X';-inkscape-font-specification:'Neuropol X';text-align:start;letter-spacing:5px;word-spacing:0px;text-anchor:start;fill:#263238;fill-opacity:1"
id="path3434" />
<path
d="m -241.91709,631.64323 q -9.3625,0 -9.3625,-5.81875 l 0,-2.49375 q 0,-5.775 9.3625,-5.775 l 18.59375,0 0,-0.65625 q 0,-3.0625 -5.38125,-3.0625 l -6.16875,0 q -2.1875,0 -2.1875,-1.70625 0,-1.75 2.1875,-1.75 l 6.16875,0 q 9.93125,0 9.93125,6.51875 l 0,8.575 q 0,6.16875 -9.5375,6.16875 l -13.60625,0 z m 13.34375,-3.4125 q 5.25,0 5.25,-2.8875 l 0,-4.76875 -18.24375,0 q -5.11875,0 -5.11875,2.66875 l 0,2.275 q 0,2.7125 5.11875,2.7125 l 12.99375,0 z"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:43.75px;font-family:'Neuropol X';-inkscape-font-specification:'Neuropol X';text-align:start;letter-spacing:5px;word-spacing:0px;text-anchor:start;fill:#263238;fill-opacity:1"
id="path3436" />
<path
d="m -205.62285,617.33698 q 0,-6.95625 11.2875,-6.95625 l 3.36875,0 q 2.23125,0 2.23125,1.79375 0,1.79375 -2.23125,1.79375 l -3.54375,0 q -6.475,0 -6.475,3.28125 l 0,12.775 q 0,1.8375 -2.31875,1.8375 -2.31875,0 -2.31875,-1.8375 l 0,-12.6875 z"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:43.75px;font-family:'Neuropol X';-inkscape-font-specification:'Neuropol X';text-align:start;letter-spacing:5px;word-spacing:0px;text-anchor:start;fill:#263238;fill-opacity:1"
id="path3438" />
</g>
</g>
</g>
</g>
<g
id="g5443-0-1-5-1-9"
transform="matrix(0.55595317,0,0,0.55595317,-521.93484,-328.66104)"
inkscape:export-filename="/home/emanuele/Desktop/logo1.png"
inkscape:export-xdpi="44.860481"
inkscape:export-ydpi="44.860481">
<g
inkscape:export-ydpi="3.4165223"
inkscape:export-xdpi="3.4165223"
transform="matrix(0.09527033,0,0,0.09527033,-1695.2716,706.62921)"
id="g8856-6-1-1-9-0-1-9">
<circle
r="1485"
cy="-1361.2571"
cx="8317.3574"
id="circle8858-1-3-7-6-5-3-0"
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:50;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
inkscape:export-xdpi="10.031387"
inkscape:export-ydpi="10.031387" />
<path
inkscape:export-ydpi="10.031387"
inkscape:export-xdpi="10.031387"
style="opacity:1;fill:#263238;fill-opacity:1;stroke:none;stroke-width:10;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 8560.3823,-1361.3029 a 242.947,242.947 0 0 1 -242.947,242.948 242.947,242.947 0 0 1 -242.947,-242.948 242.947,242.947 0 0 1 242.947,-242.946 242.947,242.947 0 0 1 242.947,242.946 z"
id="path8860-5-4-8-2-9-0-9"
inkscape:connector-curvature="0" />
<path
id="path8862-5-5-9-1-3-6-3"
d="m 9395.8755,-1984.028 a 1245.372,1245.372 0 0 0 -190.8415,-249.4971 l -280.8618,162.1556 c -87.542,-74.7796 -187.0349,-132.0588 -293.2407,-169.9527 -95.8868,97.1766 -172.0602,205.7604 -226.9672,323.8487 312.6411,-21.2772 635.5313,91.8725 935.2898,326.0721 l 176.7612,-102.0532 a 1245.372,1245.372 0 0 0 -120.1398,-290.5734 z"
clip-path="none"
mask="none"
style="fill:#1976d2;fill-opacity:1"
inkscape:connector-curvature="0"
inkscape:transform-center-x="-514.04855"
inkscape:transform-center-y="-444.04649" />
<path
inkscape:transform-center-y="265.80217"
inkscape:transform-center-x="-689.63727"
inkscape:connector-curvature="0"
style="fill:#42a5f5;fill-opacity:1"
mask="none"
clip-path="none"
d="m 9395.9474,-738.70387 a 1245.372,1245.372 0 0 0 120.6501,-290.02213 l -280.8618,-162.1557 c 20.99,-113.2034 20.8488,-228.0063 0.563,-338.9302 -132.1008,-34.4521 -264.2238,-46.1283 -393.9448,-34.635 174.7471,260.1165 238.2017,596.32248 185.2582,973.02076 l 176.7612,102.05309 a 1245.372,1245.372 0 0 0 191.5741,-249.33082 z"
id="path8864-4-8-1-2-4-4-4" />
<path
id="path8866-7-5-5-0-6-4-7"
d="m 8317.501,-115.97954 a 1245.372,1245.372 0 0 0 311.4916,-40.52501 l 0,-324.31131 c 108.5321,-38.42382 207.8837,-95.94755 293.8037,-168.97752 -36.214,-131.6287 -92.1636,-251.88868 -166.9776,-358.48372 -137.894,281.39369 -397.3296,504.44998 -750.0316,646.9487 l 0,204.10623 a 1245.372,1245.372 0 0 0 311.7139,41.24263 z"
clip-path="none"
mask="none"
style="fill:#1976d2;fill-opacity:1"
inkscape:connector-curvature="0"
inkscape:transform-center-x="-117.49007"
inkscape:transform-center-y="639.34029" />
<path
inkscape:transform-center-y="444.04652"
inkscape:transform-center-x="514.04857"
inkscape:connector-curvature="0"
style="fill:#42a5f5;fill-opacity:1"
mask="none"
clip-path="none"
d="m 7238.9827,-738.57936 a 1245.372,1245.372 0 0 0 190.8415,249.49714 l 280.8618,-162.15566 c 87.5421,74.77965 187.0349,132.05879 293.2407,169.95271 95.8868,-97.17659 172.0602,-205.76036 226.9672,-323.8487 -312.6411,21.27714 -635.5313,-91.87254 -935.2898,-326.07203 l -176.7612,102.0531 a 1245.372,1245.372 0 0 0 120.1398,290.57344 z"
id="path8868-6-7-4-7-2-7-3" />
<path
id="path8870-5-3-9-3-5-5-1"
d="m 7238.9108,-1983.9035 a 1245.372,1245.372 0 0 0 -120.6501,290.0221 l 280.8618,162.1557 c -20.99,113.2035 -20.8488,228.0063 -0.563,338.9302 132.1008,34.4521 264.2238,46.1283 393.9448,34.635 -174.7471,-260.1165 -238.2017,-596.3225 -185.2582,-973.0207 l -176.7612,-102.0532 a 1245.372,1245.372 0 0 0 -191.5741,249.3309 z"
clip-path="none"
mask="none"
style="fill:#1976d2;fill-opacity:1"
inkscape:connector-curvature="0"
inkscape:transform-center-x="689.63729"
inkscape:transform-center-y="-265.80221" />
<path
inkscape:transform-center-y="-639.34032"
inkscape:transform-center-x="117.49005"
inkscape:connector-curvature="0"
style="fill:#42a5f5;fill-opacity:1"
mask="none"
clip-path="none"
d="m 8317.3572,-2606.6279 a 1245.372,1245.372 0 0 0 -311.4915,40.525 l -1e-4,324.3113 c -108.5321,38.4239 -207.8837,95.9476 -293.8037,168.9776 36.214,131.6287 92.1637,251.8886 166.9776,358.4837 137.894,-281.3937 397.3296,-504.45 750.0316,-646.9487 l 1e-4,-204.1063 a 1245.372,1245.372 0 0 0 -311.714,-41.2426 z"
id="path8872-6-3-2-1-3-3-7" />
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 13 KiB

0
src/boot/.gitkeep Normal file
View File

13
src/boot/axios.ts Normal file
View File

@ -0,0 +1,13 @@
import axios, { AxiosInstance } from 'axios';
import { boot } from 'quasar/wrappers';
declare module 'vue/types/vue' {
interface Vue {
$axios: AxiosInstance;
}
}
export default boot(({ Vue }) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
Vue.prototype.$axios = axios;
});

View File

@ -0,0 +1,6 @@
import VueCompositionApi from '@vue/composition-api';
import { boot } from 'quasar/wrappers';
export default boot(({ Vue }) => {
Vue.use(VueCompositionApi);
});

View File

@ -0,0 +1,59 @@
<template>
<div>
<p>{{ title }}</p>
<ul>
<li v-for="todo in todos" :key="todo.id" @click="increment">
{{ todo.id }} - {{ todo.content }}
</li>
</ul>
<p>Count: {{ todoCount }} / {{ meta.totalCount }}</p>
<p>Active: {{ active ? 'yes' : 'no' }}</p>
<p>Clicks on todos: {{ clickCount }}</p>
</div>
</template>
<script lang="ts">
import {
defineComponent, PropType, computed, ref, toRef, Ref,
} from '@vue/composition-api';
import { Todo, Meta } from './models';
function useClickCount() {
const clickCount = ref(0);
function increment() {
clickCount.value += 1
return clickCount.value;
}
return { clickCount, increment };
}
function useDisplayTodo(todos: Ref<Todo[]>) {
const todoCount = computed(() => todos.value.length);
return { todoCount };
}
export default defineComponent({
name: 'CompositionComponent',
props: {
title: {
type: String,
required: true
},
todos: {
type: (Array as unknown) as PropType<Todo[]>,
default: () => []
},
meta: {
type: (Object as unknown) as PropType<Meta>,
required: true
},
active: {
type: Boolean
}
},
setup(props) {
return { ...useClickCount(), ...useDisplayTodo(toRef(props, 'todos')) };
},
});
</script>

View File

@ -1,40 +0,0 @@
<template>
<div>
<v-snackbar :timeout="0" color="error" :value="visible" top>
<v-list color="error" dense>
<v-list-item v-for="(error, index) in errors" :key="index" dense>
<v-list-item-title class="caption" style="color: white;">
{{error.message}}
</v-list-item-title>
</v-list-item>
</v-list>
<v-btn icon color="white" @click="deleteErrors">
<v-icon>
mdi-close
</v-icon>
</v-btn>
</v-snackbar>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex'
export default {
name: "ConnectionError",
methods: {
...mapActions({
deleteErrors: 'connectionError/deleteErrors'
})
},
computed: {
...mapGetters({
errors: 'connectionError/errors',
visible: 'connectionError/visible'
})
}
}
</script>
<style scoped>
</style>

View File

@ -1,48 +0,0 @@
<template>
<v-bottom-sheet persistent v-model="show" hide-overlay>
<v-card>
<v-card-title>
Cookie und Local Storage Hinweis
</v-card-title>
<v-card-text>
Diese Webseite benutzt den Local Storage. Dabei werden Daten in ihm
gespeichert, welche notwendig sind um sich einzuloggen und eingeloggt zu
bleiben. Außerdem sind diese Daten notwendig um mit dem Server zu
kommunizieren. Dabei wird ein Key 'user' angelegt, in welchem ein
Accesstoken, Benutzername, sowie der Name des Benutzers und deren Rechte
gespeichert. Dazu kommt ein Key 'cookie:accepted', falls sie diesem
zustimmen. Diese Daten bleiben solange erhalten bis Sie sich ausloggen
oder der Accesstoken abgelaufen ist und Sie ausgeloggt werden.
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn text @click="disableNotification()">Ablehnen</v-btn>
<v-btn text color="primary" @click="acceptNotification()">Akzeptieren</v-btn>
</v-card-actions>
</v-card>
</v-bottom-sheet>
</template>
<script>
import { mapGetters, mapActions } from 'vuex'
export default {
name: 'CookieNotification',
methods: {
...mapActions(['acceptNotification', 'disableNotification', 'getCookieAccepted'])
},
created() {
this.getCookieAccepted()
},
computed: {
...mapGetters({
model: 'cookieNotification',
cookie: 'cookieAccepted'
}),
show() {
return !this.cookie ? this.model : false
}
}
}
</script>
<style scoped></style>

View File

@ -0,0 +1,51 @@
<template>
<q-item
clickable
tag="a"
target="_blank"
:href="link"
>
<q-item-section
v-if="icon"
avatar
>
<q-icon :name="icon" />
</q-item-section>
<q-item-section>
<q-item-label>{{ title }}</q-item-label>
<q-item-label caption>
{{ caption }}
</q-item-label>
</q-item-section>
</q-item>
</template>
<script lang="ts">
import { defineComponent } from '@vue/composition-api';
export default defineComponent({
name: 'EssentialLink',
props: {
title: {
type: String,
required: true
},
caption: {
type: String,
default: ''
},
link: {
type: String,
default: '#'
},
icon: {
type: String,
default: ''
}
}
});
</script>

View File

@ -1,146 +0,0 @@
<template>
<v-content>
<v-container class="fill-height" fluid>
<v-row align="center" justify="center">
<v-col cols="12" sm="8" md="4">
<v-card class="elevation-12">
<v-toolbar color="blue accent-4" dark flat dense>
<v-toolbar-title>Password vergessen</v-toolbar-title>
<v-spacer />
</v-toolbar>
<v-card-text>
<v-form lazy-validation ref="reset">
<v-text-field
label="E-Mail oder Nutzername"
v-model="input"
hint="Hier bitte deinen Nutzernamen oder deine E-Mail angeben. Sollte eins der beiden Daten gefunden werden, wird an deine hinterlegte E-Mail ein Password zum Zurücksetzen gesendet."
persistent-hint
:prepend-icon="prependIcon"
required
:rules="[notEmpty, isMail ? email : true]"
@keyup.enter="resetPassword()"
>
</v-text-field>
</v-form>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn
color="primary"
@click="resetPassword()"
@submit.prevent="resetPassword()"
>
Zurücksetzen
</v-btn>
</v-card-actions>
</v-card>
</v-col>
</v-row>
</v-container>
<v-snackbar bottom :timeout="0" :value="response.value" :color="response.error? 'error' : 'success'">
{{ response.message }}
<v-btn icon @click="response.value = false">
<v-icon color="white">
mdi-close
</v-icon>
</v-btn>
</v-snackbar>
</v-content>
</template>
<script>
import axios from 'axios'
import url from '@/plugins/routes'
export default {
name: 'ResetPassword',
data() {
return {
input: '',
response: {
error: false,
value: false,
message: null
},
defaultResponse: {
error: false,
value: false,
message: null
},
notEmpty: data => {
return data ? true : 'Darf nicht leer sein.'
},
email: value => {
if (value.length > 0) {
const pattern = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
return pattern.test(value) || 'keine gültige E-Mail'
}
return true
}
}
},
methods: {
resetPassword() {
if (this.$refs.reset.validate()) {
console.log(this.input, this.isMail)
if (this.isMail) {
axios
.post(url.resetPassword, { mail: this.input })
.then(data => {
console.log(data)
this.setMessage(data.data.mail, false)
})
.catch(error => {
console.log(error)
this.setMessage(error, true)
})
.finally(() => {
this.$refs.reset.reset()
})
} else {
axios
.post(url.resetPassword, { username: this.input })
.then(data => {
console.log(data)
this.setMessage(data.data.mail, false)
})
.catch(error => {
console.log(error)
this.setMessage(error, true)
})
.finally(() => {
this.$refs.reset.reset()
})
}
}
},
setMessage(mail, error) {
if (error) {
this.response.error = true
this.response.value = true
this.response.message =
'Es ist ein Fehler aufgetreten. Wende dich an einen Administrator oder probiere es erneut.'
} else {
this.response.error = false
this.response.value = true
this.response.message = `Es wurde ein neues Password an ${mail} versendet`
}
}
},
computed: {
prependIcon() {
if (this.input) {
return this.input.includes('@') ? 'mdi-email' : 'mdi-account'
}
return 'mdi-account'
},
isMail() {
if (this.input) {
return this.input.includes('@')
}
return false
}
}
}
</script>
<style scoped></style>

View File

@ -1,94 +0,0 @@
<template>
<div>
<v-app-bar
app
clipped-left
clipped-right
color="blue accent-4"
class="elevation-4"
dark
dense
>
<v-btn icon @click="reload()">
<v-img src="@/assets/logo-64.png" contain height="40"></v-img>
</v-btn>
<v-toolbar-title>Flaschengeist <span class="caption">v1.1.0 beta 0</span></v-toolbar-title>
<v-spacer/>
<v-btn icon v-if="getRouteName == 'resetPassword'" @click="goTo('login')">
<v-icon>
mdi-home
</v-icon>
</v-btn>
<v-btn icon v-if="getRouteName == 'priceListNoLogin'" @click="goBack()">
<v-icon>
{{ back }}
</v-icon>
</v-btn>
<v-btn icon v-if="isFinanzer" :disabled="locked" @click="goTo('overview')">
<v-icon>{{ attach_money }}</v-icon>
</v-btn>
<v-btn icon v-if="isGastro" :disabled="locked" @click="goTo('gastroPricelist')">
<v-icon>{{ gastro }}</v-icon>
</v-btn>
<v-btn icon v-if="isBar" @click="goTo('geruecht')">
<v-icon>{{ local_bar }}</v-icon>
</v-btn>
<v-btn icon v-if="isUser" :disabled="locked" @click="goTo('add')">
<v-icon>{{ person }}</v-icon>
</v-btn>
<v-btn icon @click="goTo('priceListNoLogin')">
<v-icon>{{ list }}</v-icon>
</v-btn>
</v-app-bar>
<ConnectionError/>
</div>
</template>
<script>
import { mapActions, mapGetters } from 'vuex'
import {
mdiCurrencyEur,
mdiGlassCocktail,
mdiAccount,
mdiFileMultiple,
mdiFoodForkDrink,
mdiArrowLeftBoldCircle
} from '@mdi/js'
import ConnectionError from "@/components/ConnectionError";
export default {
name: 'TitleBar',
components: {ConnectionError},
data() {
return {
attach_money: mdiCurrencyEur,
local_bar: mdiGlassCocktail,
person: mdiAccount,
list: mdiFileMultiple,
gastro: mdiFoodForkDrink,
back: mdiArrowLeftBoldCircle
}
},
computed: {
...mapGetters(['isBar', 'isFinanzer', 'isUser', 'isLoggedIn', 'isGastro']),
...mapGetters({locked: 'barUsers/locked'}),
getRouteName() {
return this.$route.name
}
},
methods: {
...mapActions(['logout']),
reload() {
location.reload()
},
goTo(name) {
this.$router.push({name: name})
},
goBack() {
window.history.length > 1 ? this.$router.go(-1) : this.$router.push({name: 'main'})
},
}
}
</script>
<style scoped></style>

View File

@ -1,446 +0,0 @@
<template>
<div>
<v-container v-if="!first_loading">
<v-card class="mx-auto" outlined :loading="loading">
<v-card-title>
<div class="title">Dienstgetränke</div>
<v-chip class="ma-2" color="red" text-color="white" v-if="locked"
>Gesperrt</v-chip
>
<v-btn class="menuBtn" @click.stop="isMenuShow = !isMenuShow" icon>
<v-icon>{{ menuIcon }}</v-icon>
</v-btn>
</v-card-title>
<v-card-subtitle v-if="limit - free_drink_list_history_job_credit > 0">
Noch
{{ ((limit - free_drink_list_history_job_credit) / 100).toFixed(2) }}
übrig!!
</v-card-subtitle>
<v-card-text>
<v-row v-if="!first_loading">
<v-col cols="12">
<v-row>
<v-col
v-for="freeDrink in free_drink_list_config_job"
:key="freeDrink.id"
cols="6"
xs="3"
sm="4"
class="drinkCol"
>
<v-btn
class="drinkBtn"
block
:disabled="locked"
:color="color"
@click="addAmount(freeDrink, freeDrinkTypeJob)"
>{{ freeDrink.label }}</v-btn
>
</v-col>
</v-row>
<v-row v-if="!first_loading" class="justify-end">
<v-col cols="3">
<v-list-item>
<v-list-item-content class="text-center">
<v-list-item-action-text :class="getColor()">
{{
(free_drink_list_history_job_credit / 100).toFixed(2)
}}
</v-list-item-action-text>
</v-list-item-content>
</v-list-item>
</v-col>
</v-row>
</v-col>
</v-row>
</v-card-text>
</v-card>
<v-card class="mx-auto" outlined :loading="loading">
<v-card-title>
<div class="title">Bandgetränke</div>
<v-btn class="menuBtn" @click.stop="isMenuShow = !isMenuShow" icon>
<v-icon>{{ menuIcon }}</v-icon>
</v-btn>
</v-card-title>
<v-card-text>
<v-row v-if="!first_loading">
<v-col cols="12">
<v-row>
<v-col
v-for="freeDrink in free_drink_list_config_band"
:key="freeDrink.id"
cols="6"
xs="3"
sm="4"
class="drinkCol"
>
<v-btn
class="drinkBtn"
block
:color="color_fix"
@click="addAmount(freeDrink, freeDrinkTypeBand)"
>{{ freeDrink.label }}</v-btn
>
</v-col>
</v-row>
<v-row v-if="!first_loading" class="justify-end">
<v-col cols="3">
<v-list-item>
<v-list-item-content class="text-center">
<v-list-item-action-text class="title">
{{
(free_drink_list_history_band_without_canceled_price/100).toFixed(2)
}}
</v-list-item-action-text>
</v-list-item-content>
</v-list-item>
</v-col>
</v-row>
</v-col>
</v-row>
</v-card-text>
</v-card>
</v-container>
<v-navigation-drawer v-model="isMenuShow" right app clipped>
<v-list-item-group :key="componentRenderer">
<v-list-item inactive>
<v-list-item-title class="headline">Verlauf</v-list-item-title>
</v-list-item>
<v-divider />
<div
v-for="freeDrinkHistory in free_drink_list_history_bar"
:key="freeDrinkHistory.id"
>
<v-list-item
three-line
inactive
@click="canceledAmount(freeDrinkHistory)"
>
<v-list-item-content>
<v-list-item-title>{{
now(freeDrinkHistory.timestamp)
}}</v-list-item-title>
<v-list-item-subtitle
>{{ freeDrinkHistory.free_drink_type.name }}:
{{ freeDrinkHistory.free_drink_config.label }} wurde für
{{
(freeDrinkHistory.free_drink_config.price / 100).toFixed(2)
}}
hinzugefügt.
</v-list-item-subtitle>
<v-list-item-subtitle
class="red--text"
v-if="freeDrinkHistory.canceled"
>STORNIERT!!!</v-list-item-subtitle
>
<v-list-item-action-text
v-if="
isStronoEnabled(freeDrinkHistory.timestamp) &&
!freeDrinkHistory.canceled
"
>Klicken um zu Stornieren</v-list-item-action-text
>
</v-list-item-content>
</v-list-item>
</div>
</v-list-item-group>
</v-navigation-drawer>
<v-dialog v-model="showConfirmCanceledDialog" max-width="290">
<v-card>
<v-card-title>Willst du wirklich??</v-card-title>
<v-card-text v-if="canceledMessage">
{{ canceledMessage.free_drink_type.name }}: Willst du wirklich ein
{{ canceledMessage.free_drink_config.label }} im Wert von
{{ (canceledMessage.free_drink_config.price / 100).toFixed(2) }}
stornieren?
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn text @click="cancelCanceled">Abbrechen</v-btn>
<v-btn text @click="acceptCanceled">Stornieren</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<v-container v-if="first_loading">
<AddAmountSkeleton />
</v-container>
<v-snackbar
:color="
snackbar_messages.length > 0
? snackbar_messages[0].error
? 'error'
: 'success'
: 'success'
"
bottom
:timeout="0"
:multi-line="true"
:value="snackbar_messages.length > 0 ? snackbar_messages[0].visible : null"
vertical
>
<v-list-item
v-for="(message, index) in snackbar_messages"
:key="index"
:style="
message.error
? 'background-color: #FF5252;'
: 'background-color: #4CAF50;'
"
v-show="message.visible"
>
<v-list-item-content>
<v-list-item-title style="color: white">
{{ createMessage(message) }}
</v-list-item-title>
</v-list-item-content>
<v-list-item-action v-if="message.error">
<v-btn icon @click="message.visible = false">
<v-icon color="white">
mdi-close
</v-icon>
</v-btn>
</v-list-item-action>
</v-list-item>
</v-snackbar>
</div>
</template>
<script>
// eslint-disable-next-line no-unused-vars
import AddAmountSkeleton from '../user/Skeleton/AddAmountSkeleton'
import { mapActions, mapGetters } from 'vuex'
import { mdiPlus, mdiMenu } from '@mdi/js'
export default {
name: 'BarFreedrinks',
components: { AddAmountSkeleton },
created() {
this.timer = setInterval(() => (this.componentRenderer += 1), 1000)
this.get_free_drink_list_config()
this.get_free_drink_list_history()
},
data() {
return {
plus: mdiPlus,
menuIcon: mdiMenu,
showConfirmCanceledDialog: false,
canceledMessage: null,
limit: 1000,
amount: 0,
customValue: null,
color: 'green accent-4',
color_fix: 'green accent-4',
timer: '',
componentRenderer: 0,
isMenuShow: false,
freeDrinkTypeJob: 1,
freeDrinkTypeBand: 3
}
},
methods: {
...mapActions('freeDrinkList', [
'get_free_drink_list_config',
'get_free_drink_list_history',
'set_free_drink_list_history',
'update_free_drink_list_history'
]),
output() {
console.log(this.free_drink_list_config)
},
addAmount(freeDrink, free_drink_type_id) {
this.set_free_drink_list_history({ ...freeDrink, free_drink_type_id })
/*if (amount) {
this.amount += amount
this.generateMessage(amount)
}
this.checkLocked()*/
},
canceledAmount(historyElement) {
if (!this.isStronoEnabled(historyElement.timestamp)) {
return
}
this.showConfirmCanceledDialog = true
this.canceledMessage = historyElement
},
cancelCanceled() {
this.showConfirmCanceledDialog = null
this.canceledMessage = null
},
generateMessage(amount) {
this.messages.push({
date: new Date(),
canceled: false,
amount: amount,
error: false,
visible: true
})
},
createMessage(message) {
var text = ''
if (message.error) {
text =
'ERROR: ' + message.free_drink_type.name +': Konnte ' + message.label + ' für ' +
(message.price / 100).toFixed(2) +
'€ nicht hinzufügen.'
} else if (message.canceled) {
text = `${message.free_drink_type.name}: ${message.label} wurde für ${(message.price/100).toFixed(2)}€ storniert.`
} else {
text = message.free_drink_type.name + ': ' + message.label + ' wurde für ' + (message.price / 100).toFixed(2) + '€ hinzugefügt.'
}
return text
},
checkLocked() {
this.locked = this.limit - this.amount <= 0
},
getColor() {
return this.locked ? 'title red--text' : 'title'
},
acceptCanceled() {
this.canceledMessage.canceled = true
this.update_free_drink_list_history(this.canceledMessage)
this.cancelCanceled()
},
acceptStorno() {
this.stornoMessage.storno = true
this.amount -= this.stornoMessage.amount
console.log(this.amount, this.stornoMessage)
this.cancelStorno()
},
stornoAmount(message) {
if (!this.isStronoEnabled(message.date) || message.storno) {
return
}
this.showConfirmStornoDialog = true
this.stornoMessage = message
},
cancelStorno() {
this.showConfirmStornoDialog = null
this.stornoMessage = null
},
},
computed: {
...mapGetters('freeDrinkList', [
'free_drink_list_config_job',
'free_drink_list_config_band',
'free_drink_list_history',
'free_drink_list_history_job_credit',
'free_drink_list_history_band',
'free_drink_list_history_band_without_canceled',
'loading',
'snackbar_messages'
]),
now() {
return now => {
var actual = new Date()
var zero = new Date(0)
var date = new Date(actual - now)
if (date.getFullYear() === zero.getFullYear()) {
if (date.getMonth() === zero.getMonth()) {
if (date.getDate() === zero.getDate()) {
if (date.getHours() === zero.getDate()) {
if (date.getMinutes() < 1) {
return 'vor ' + date.getSeconds() + ' Sekunden'
} else if (date.getMinutes() < 10) {
return 'vor ' + date.getMinutes() + ' Minuten'
} else {
return (
(now.getHours() < 10 ? '0' : '') +
now.getHours() +
':' +
(now.getMinutes() < 10 ? '0' : '') +
now.getMinutes()
)
}
} else {
return (
(now.getHours() < 10 ? '0' : '') +
now.getHours() +
':' +
(now.getMinutes() < 10 ? '0' : '') +
now.getMinutes()
)
}
}
}
}
return (
now.getDate() +
'.' +
now.getMonth() +
'.' +
now.getFullYear() +
' ' +
(now.getHours() < 10 ? '0' : '') +
now.getHours() +
':' +
(now.getMinutes() < 10 ? '0' : '') +
now.getMinutes()
)
}
},
isStronoEnabled() {
return now => {
var actual = new Date()
return actual - now < 60000
}
},
locked() {
return this.free_drink_list_history_job_credit >= 1000
},
first_loading() {
return (
this.loading &&
this.free_drink_list_history.length == 0 &&
this.free_drink_list_config_band.length == 0 &&
this.free_drink_list_config_job.length == 0
)
},
free_drink_list_history_band_without_canceled_price() {
let sum = 0
this.free_drink_list_history_band_without_canceled.forEach(item => {
sum += item.free_drink_config.price
})
return sum
},
free_drink_list_history_bar() {
return this.free_drink_list_history.filter(item => {
return item.free_drink_type.id == 1 || item.free_drink_type.id == 3
})
}
},
beforeDestroy() {
clearInterval(this.timer)
}
}
</script>
<style scoped>
.drinkBtn {
width: 100%;
}
.drinkCol {
padding: 6px !important;
}
.title {
width: calc(100% - 135px);
min-width: 150px;
font-size: 1.25rem !important;
font-weight: 500;
line-height: 2rem;
letter-spacing: 0.0125em !important;
font-family: 'Roboto', sans-serif !important;
}
.menuBtn {
right: 15px;
position: absolute;
}
.history-item {
cursor: pointer;
}
</style>

View File

@ -1,35 +0,0 @@
<template>
<v-list>
<v-list-item link :to="{name: 'geruecht'}">
<v-list-item-icon>
<v-icon>{{ glass_mug_variant }}</v-icon>
</v-list-item-icon>
<v-list-item-title>
Geruecht
</v-list-item-title>
</v-list-item>
<v-list-item link :to="{name: 'baruserFreedrinks'}">
<v-list-item-icon>
<v-icon>{{ beer }}</v-icon>
</v-list-item-icon>
<v-list-item-title>
Freigetränke
</v-list-item-title>
</v-list-item>
</v-list>
</template>
<script>
import { mdiGlassMugVariant,mdiBeer } from '@mdi/js'
export default {
name: 'BarNavigation',
data() {
return {
glass_mug_variant: mdiGlassMugVariant,
beer: mdiBeer
}
}
}
</script>
<style scoped></style>

View File

@ -1,595 +0,0 @@
<template>
<div>
<v-dialog v-model="checkValidate" max-width="290">
<v-card>
<v-card-title>
Willst du wirklich??
</v-card-title>
<v-card-text v-if="stornoMessage">
Willst du wirklich den Betrag
{{ (stornoMessage.amount / 100).toFixed(2) }} von
{{ stornoMessage.user.firstname }}
{{ stornoMessage.user.lastname }} stornieren?
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn text @click="cancelStorno">Abbrechen</v-btn>
<v-btn text @click="acceptStorno">Stornieren</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<v-dialog v-model="dialog" max-width="290">
<v-card>
<v-card-title class="headline"
>Transaktion ist länger als 1 Minute her!</v-card-title
>
<v-card-text>
Da die Transaktion länger als 1 Minuter her ist, kann eine Stornierung
nicht durchgeführt werden. Wende dich bitte an den Finanzer.
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn text @click="dialog = false">
Verstanden
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<v-dialog
v-if="overLimitUser"
v-model="overLimitUser"
max-width="290"
persistent
>
<v-card>
<v-card-title>Warnung</v-card-title>
<v-card-text>
{{ overLimitUser.firstname }} {{ overLimitUser.lastname }} übersteigt
das Anschreibelimit von
{{ (overLimitUser.limit / 100).toFixed(2) }} . Danach kann dieses
Mitglied nichts mehr anschreiben. Will er das wirklich?
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn text @click="cancel()">Abbrechen</v-btn>
<v-btn text @click="continueAdd(overLimitUser)">Anschreiben</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<v-dialog
v-if="overOverLimit"
v-model="overOverLimit"
max-width="290"
persistent
>
<v-card>
<v-card-title>Anschreiben nicht möglich</v-card-title>
<v-card-text>
{{ overOverLimit.firstname }}
{{ overOverLimit.lastname }} überschreitet das Anschreibelimit zuviel.
Das Anschreiben wurde daher gestoppt und zurückgesetzt.
</v-card-text>
<v-card-actions>
<v-btn text @click="overOverLimit = null">Verstanden</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<v-progress-linear v-if="loading && users.length !== 0" indeterminate />
<v-container>
<AddAmountSkeleton v-if="loading && users.length === 0" />
</v-container>
<v-navigation-drawer v-model="menu" right app clipped>
<v-list-item-group :key="componentRenderer">
<v-list-item inactive>
<v-list-item-title class="headline">
Verlauf
</v-list-item-title>
</v-list-item>
<v-divider />
<div
v-for="message in messages"
three-line
:key="messages.indexOf(message)"
>
<v-list-item three-line inactive @click="storno(message)">
<v-list-item-content>
<v-progress-linear indeterminate v-if="message.loading" />
<v-list-item-title>{{ now(message.date) }}</v-list-item-title>
<v-list-item-subtitle>{{
createMessage(message)
}}</v-list-item-subtitle>
<v-list-item-subtitle class="red--text" v-if="message.storno">
STORNIERT!!!
</v-list-item-subtitle>
<v-list-item-subtitle class="red--text" v-else-if="message.error">
ERROR!
</v-list-item-subtitle>
<v-list-item-action-text v-if="under5minutes(message.date)"
>Klicken um zu Stornieren
</v-list-item-action-text>
</v-list-item-content>
</v-list-item>
</div>
</v-list-item-group>
</v-navigation-drawer>
<div v-for="user in users" :key="users.indexOf(user)">
<div v-if="isFiltered(user) && calcLastSeen(user)">
<v-container>
<v-card :loading="user.loading">
<v-card-title>
<v-list-item-title class="title"
>{{ user.firstname }} {{ user.lastname }}</v-list-item-title
>
</v-card-title>
<v-card-subtitle v-if="user.limit + user.amount > 0">
Nur noch {{ ((user.limit + user.amount) / 100).toFixed(2) }}
übrig!!
</v-card-subtitle>
<v-card-text>
<v-row v-if="!user.locked">
<v-col cols="10">
<v-row>
<v-col cols="6" xs="5" sm="4">
<v-btn
class="creditBtn"
block
@click="addingAmount(user, 200)"
:color="color"
:disabled="user.locked"
>2
</v-btn>
</v-col>
<v-col cols="6" xs="5" sm="4">
<v-btn
class="creditBtn"
block
@click="addingAmount(user, 100)"
:color="color"
:disabled="user.locked"
>1
</v-btn>
</v-col>
<v-col cols="6" xs="5" sm="4">
<v-btn
class="creditBtn"
block
@click="addingAmount(user, 50)"
:color="color"
:disabled="user.locked"
>0,50
</v-btn>
</v-col>
<v-col cols="6" xs="5" sm="4">
<v-btn
class="creditBtn"
block
@click="addingAmount(user, 40)"
:color="color"
:disabled="user.locked"
>0,40
</v-btn>
</v-col>
<v-col cols="6" xs="5" sm="4">
<v-btn
class="creditBtn"
block
@click="addingAmount(user, 20)"
:color="color"
:disabled="user.locked"
>0,20
</v-btn>
</v-col>
<v-col cols="6" xs="5" sm="4">
<v-btn
class="creditBtn"
block
@click="addingAmount(user, 10)"
:color="color"
:disabled="user.locked"
>0,10
</v-btn>
</v-col>
<v-col cols="8">
<v-text-field
outlined
type="number"
v-model="user.value"
label="Benutzerdefinierter Betrag"
:disabled="user.locked"
></v-text-field>
</v-col>
<v-col cols="4">
<v-btn
fab
:color="color"
@click="addAmountMore(user)"
:disabled="user.locked"
>
<v-icon>{{ plus }}</v-icon>
</v-btn>
</v-col>
</v-row>
</v-col>
<v-col align-self="center">
<v-row>
<v-list-item>
<v-list-item-content class="text-center">
<v-list-item-action-text :class="getColor(user.type)"
>{{ (user.amount / 100).toFixed(2) }}
</v-list-item-action-text>
<v-list-item-action-text v-if="user.toSetAmount">
- {{ (user.toSetAmount / 100).toFixed(2) }}
</v-list-item-action-text>
</v-list-item-content>
</v-list-item>
</v-row>
</v-col>
</v-row>
<v-row>
<v-col class="hidden-sm-and-down" cols="80">
<v-alert v-if="user.locked" type="error"
>{{ user.firstname }} darf nicht mehr anschreiben.
{{ user.firstname }} sollte sich lieber mal beim Finanzer
melden.
</v-alert>
</v-col>
<v-col align-self="center" v-if="user.locked">
<v-row>
<v-list-item>
<v-list-item-content class="text-center">
<v-list-item-action-text :class="getColor(user.type)"
>{{ (user.amount / 100).toFixed(2) }}
</v-list-item-action-text>
</v-list-item-content>
</v-list-item>
</v-row>
</v-col>
</v-row>
</v-card-text>
</v-card>
<v-snackbar
:color="
messages.length > 0
? messages[0].error
? 'error'
: 'success'
: 'success'
"
bottom
:timeout="0"
:multi-line="true"
:value="messages.length > 0 ? messages[0].visible : test"
vertical
>
<v-list-item
v-for="message in messages"
:key="messages.indexOf(message)"
:style="
message.error
? 'background-color: #FF5252;'
: 'background-color: #4CAF50;'
"
v-show="message.visible"
>
<v-list-item-content>
<v-list-item-title style="color: white">
{{ createMessage(message) }}
</v-list-item-title>
</v-list-item-content>
<v-list-item-action v-if="message.error">
<v-btn icon @click="message.visible = false">
<v-icon color="white">
mdi-close
</v-icon>
</v-btn>
</v-list-item-action>
</v-list-item>
</v-snackbar>
</v-container>
</div>
</div>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex'
import { mdiPlus } from '@mdi/js'
import AddAmountSkeleton from '../user/Skeleton/AddAmountSkeleton'
export default {
name: 'CreditLists',
components: { AddAmountSkeleton },
props: {},
data() {
return {
plus: mdiPlus,
value: null,
color: 'green accent-4',
menu: true,
dialog: false,
componentRenderer: 0,
timer: '',
stornoMessage: null,
checkValidate: false,
test: null,
overLimitUser: null,
overOverLimit: null
}
},
created() {
this.menu = this.menu_from_store
this.getUsers()
this.timer = setInterval(this.forceRender, 1000)
},
methods: {
...mapActions({
addAmount: 'barUsers/addAmount',
getUsers: 'barUsers/getUsers',
deactivate: 'barUsers/deactivateMenu',
commitStorno: 'barUsers/storno'
}),
continueAdd(user) {
this.overLimitUser = null
user.checkedOverLimit = true
if (user.value) {
this.addAmount({
username: user.username,
amount: Math.round(Math.abs(user.value * 100)),
user: user
})
setTimeout(() => {
user.value = null
user.toSetAmount = null
}, 300)
} else {
user.timeout = setTimeout(() => {
this.addAmount({
username: user.username,
amount: user.toSetAmount,
user: user
})
setTimeout(() => {
user.toSetAmount = null
}, 300)
}, 2000)
}
},
cancel() {
this.overLimitUser.toSetAmount = null
this.overLimitUser.value = null
this.overLimitUser = null
},
checkOverLimitIsValid(user) {
console.log(user)
if (user.toSetAmount && user.autoLock) {
if (
(user.amount - Number.parseInt(user.toSetAmount)) <
-(user.limit + 500)
) {
this.overOverLimit = user
user.toSetAmount = null
user.value = null
return false
}
}
return true
},
checkOverLimit(user) {
console.log(user)
if (user.toSetAmount) {
if ((user.amount - user.toSetAmount) < -user.limit) {
return user.checkedOverLimit ? false : true
}
}
return false
},
addingAmount(user, amount) {
clearTimeout(user.timeout)
user.toSetAmount = user.toSetAmount ? user.toSetAmount + amount : amount
if (this.checkOverLimitIsValid(user)) {
if (this.checkOverLimit(user) && user.autoLock) {
this.overLimitUser = user
} else {
user.timeout = setTimeout(() => {
this.addAmount({
username: user.username,
amount: user.toSetAmount,
user: user
})
setTimeout(() => {
user.toSetAmount = null
}, 300)
}, 2000)
}
}
},
forceRender() {
this.componentRenderer += 1
},
getColor(type) {
return type === 'credit' ? 'title green--text' : 'title red--text'
},
isFiltered(user) {
try {
var filters = this.filter.split(' ')
if (filters.length === 1) {
if (
user.firstname.toLowerCase().includes(filters[0].toLowerCase()) ||
user.lastname.toLowerCase().includes(filters[0].toLowerCase())
) {
return true
}
} else if (filters.length > 1) {
if (
user.firstname.toLowerCase().includes(filters[0].toLowerCase()) &&
user.lastname.toLowerCase().includes(filters[1].toLowerCase())
) {
return true
}
}
return false
} catch (e) {
return true
}
},
addAmountMore(user) {
user.toSetAmount = user.toSetAmount
? user.toSetAmount + Math.round(Math.abs(user.value * 100))
: Math.round(Math.abs(user.value * 100))
if (this.checkOverLimitIsValid(user)) {
if (this.checkOverLimit(user) && user.autoLock) {
this.overLimitUser = user
} else {
this.addAmount({
username: user.username,
amount: Math.round(Math.abs(user.value * 100)),
user: user
})
setTimeout(() => {
user.value = null
user.toSetAmount = null
}, 300)
}
}
},
storno(message) {
if (!message.error) {
if (!this.under5minutes(message.date)) this.dialog = true
else {
this.checkValidate = true
this.stornoMessage = message
}
}
},
acceptStorno() {
this.commitStorno({
username: this.stornoMessage.user.username,
amount: this.stornoMessage.amount,
date: this.stornoMessage.date
})
setTimeout(() => {
this.cancelStorno()
}, 300)
},
cancelStorno() {
this.stornoMessage = null
this.checkValidate = null
},
createMessage(message) {
var text = ''
if (message.error) {
text =
'ERROR: Konnte ' +
(message.amount / 100).toFixed(2) +
'€ nicht zu ' +
message.user.firstname +
' ' +
message.user.lastname +
' hinzufügen.'
} else {
text =
'' +
(message.amount / 100).toFixed(2) +
'€ wurde zu ' +
message.user.firstname +
' ' +
message.user.lastname +
' hinzugefügt.'
}
return text
},
calcLastSeen(user) {
if (user.last_seen) {
let date = new Date()
if ((date - user.last_seen) / 1000 / 60 / 60 < 72) {
return true
}
}
return false
}
},
computed: {
...mapGetters({
users: 'barUsers/users',
filter: 'barUsers/filter',
loading: 'barUsers/usersLoading',
messages: 'barUsers/messages',
menu_from_store: 'barUsers/menu'
}),
under5minutes() {
return now => {
var actual = new Date()
return actual - now < 60000
}
},
now() {
return now => {
var actual = new Date()
var zero = new Date(0)
var date = new Date(actual - now)
if (date.getFullYear() === zero.getFullYear()) {
if (date.getMonth() === zero.getMonth()) {
if (date.getDate() === zero.getDate()) {
if (date.getHours() === zero.getDate()) {
if (date.getMinutes() < 1) {
return 'vor ' + date.getSeconds() + ' Sekunden'
} else if (date.getMinutes() < 10) {
return 'vor ' + date.getMinutes() + ' Minuten'
} else {
return (
(now.getHours() < 10 ? '0' : '') +
now.getHours() +
':' +
(now.getMinutes() < 10 ? '0' : '') +
now.getMinutes()
)
}
} else {
return (
(now.getHours() < 10 ? '0' : '') +
now.getHours() +
':' +
(now.getMinutes() < 10 ? '0' : '') +
now.getMinutes()
)
}
}
}
}
return (
now.getDate() +
'.' +
now.getMonth() +
'.' +
now.getFullYear() +
' ' +
(now.getHours() < 10 ? '0' : '') +
now.getHours() +
':' +
(now.getMinutes() < 10 ? '0' : '') +
now.getMinutes()
)
}
}
},
watch: {
menu(newValue) {
if (!newValue) this.deactivate()
},
menu_from_store() {
this.menu = this.menu_from_store
}
},
beforeDestroy() {
clearInterval(this.timer)
}
}
</script>
<style scoped>
.creditBtn {
margin: 2px;
}
</style>

View File

@ -1,133 +0,0 @@
<template>
<div>
<v-toolbar>
<v-spacer />
<v-toolbar-items>
<v-autocomplete
outlined
return-object
v-model="user"
style="margin-top: 3px"
placeholder="Suche Person"
:items="allUsers"
item-text="fullName"
full-width
:loading="loading"
:search-input.sync="filter"
clearable
>
<template v-slot:prepend-inner>
<v-icon>{{ search_person }}</v-icon>
</template>
<template v-slot:item="data">
<v-list-item-icon v-if="getLocked(data.item)">
<v-icon>mdi-alert</v-icon>
</v-list-item-icon>
<v-list-item-content>
{{data.item.fullName}}
<v-spacer/>
{{(getCredit(data.item)/100).toFixed(2)}}
</v-list-item-content>
</template>
</v-autocomplete>
<v-btn text @click="addUser">Hinzufügen</v-btn>
<v-btn v-if="!locked" text @click="lock">Sperren</v-btn>
<v-btn v-else text @click="overlay = true">Entsperren</v-btn>
<v-btn @click="clickMenu" icon>
<v-icon>{{ menuIcon }}</v-icon>
</v-btn>
</v-toolbar-items>
</v-toolbar>
<v-dialog v-model="overlay">
<v-card>
<v-card-title>Entsperre Baransicht</v-card-title>
<v-card-text>
<v-text-field outlined type="password" label="Passwort" v-model="password"></v-text-field>
</v-card-text>
<v-card-actions>
<v-spacer/>
<v-btn text @click="overlay = false">Abbrechen</v-btn>
<v-btn text @click="doUnlock">Entsperren</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex'
import { mdiAccountSearch, mdiMenu, mdiAlert } from '@mdi/js'
export default {
name: 'SearchBar',
props: {},
data() {
return {
user: null,
filter: '',
search_person: mdiAccountSearch,
menuIcon: mdiMenu,
alert: mdiAlert,
overlay: false,
password: ''
}
},
created() {
this.getAllUsers()
this.getLocked()
},
methods: {
...mapActions({
getAllUsers: 'barUsers/getAllUsers',
addCreditList: 'barUsers/addCreditList',
setFilter: 'barUsers/setFilter',
activateMenu: 'barUsers/activateMenu',
deactivateMenu: 'barUsers/deactivateMenu',
lock: 'barUsers/setLocked',
unlock: 'barUsers/unlock',
getLocked: 'barUsers/getLocked'
}),
addUser() {
this.addCreditList(this.user)
},
clickMenu() {
if (this.menu) this.deactivateMenu()
else this.activateMenu()
},
doUnlock() {
this.unlock(this.password)
this.password = ''
this.overlay = false
},
getCredit(user) {
let retUser = this.users.find(item => {
return item.username === user.username
})
return retUser ? retUser.amount : 0
},
getLocked(user) {
if(!user) return
let retUser = this.users.find(item => {
return item.username === user.username
})
return retUser ? retUser.locked : false
}
},
computed: {
...mapGetters({
allUsers: 'barUsers/allUsers',
users: 'barUsers/users',
loading: 'barUsers/allUsersLoading',
menu: 'barUsers/menu',
locked: 'barUsers/locked'
})
},
watch: {
filter(val) {
this.setFilter(val)
}
}
}
</script>
<style scoped></style>

View File

@ -1,49 +0,0 @@
<template>
<div>
<v-list>
<v-list-item class="title" link :to="{name: 'overview'}">
<v-list-item-icon>
<v-icon>{{home}}</v-icon>
</v-list-item-icon>
<v-list-item-title>Gesamtübersicht</v-list-item-title>
</v-list-item>
</v-list>
<v-divider />
<v-list>
<div v-for="user in users" v-bind:key="users.indexOf(user)">
<v-list-item
:to="{ name: 'activeUser', params: { id: user.username } }"
link
>
<v-list-item-title
>{{ user.lastname }}, {{ user.firstname }}</v-list-item-title
>
</v-list-item>
</div>
<v-list-item>
<v-progress-circular indeterminate color="grey" v-if="loading" />
</v-list-item>
</v-list>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import {mdiHome} from '@mdi/js'
export default {
name: 'FinanzerNavigation',
data () {
return {
home: mdiHome
}
},
computed: {
...mapGetters({
users: 'finanzerUsers/users',
loading: 'finanzerUsers/usersLoading'
})
}
}
</script>
<style scoped></style>

View File

@ -1,400 +0,0 @@
<template>
<v-content>
<v-toolbar tile>
<v-toolbar-title>Gesamtübersicht</v-toolbar-title>
<v-spacer />
<v-toolbar-items>
<v-btn text icon @click="countYear(false)">
<v-icon>{{keyboard_arrow_left}}</v-icon>
</v-btn>
<v-list-item>
<v-list-item-title class="title">{{ year }}</v-list-item-title>
</v-list-item>
<v-btn text icon @click="countYear(true)" :disabled="isActualYear">
<v-icon>{{keyboard_arrow_right}}</v-icon>
</v-btn>
</v-toolbar-items>
<v-spacer />
<v-toolbar-items>
<v-btn text @click="sendMails">Emails senden</v-btn>
<v-autocomplete
outlined
return-object
v-model="user"
style="margin-top: 3px"
placeholder="Suche Person"
:items="allUsers"
item-text="fullName"
full-width
:loading="allUsersLoading"
:search-input.sync="filter"
@change="addToUser(user)"
>
<template v-slot:prepend-inner>
<v-icon>{{search_person}}</v-icon>
</template>
</v-autocomplete>
</v-toolbar-items>
</v-toolbar>
<v-expand-transition>
<v-card style="margin-top: 3px" v-show="errorMails">
<v-row>
<v-spacer />
<v-btn
text
icon
style="margin-right: 5px"
@click="errorExpand ? (errorExpand = false) : (errorExpand = true)"
>
<v-icon :class="isExpand(errorExpand)" dense>$expand</v-icon>
</v-btn>
</v-row>
<v-expand-transition>
<div v-show="errorExpand">
<v-alert
v-for="error in errorMails"
:key="errorMails.indexOf(error)"
dense
:type="computeError(error.error)"
>{{ errorMessage(error) }}</v-alert
>
</div>
</v-expand-transition>
</v-card>
</v-expand-transition>
<v-progress-linear v-if="loading && users.length !== 0" indeterminate />
<TableSkeleton v-if="loading && users.length === 0" />
<div v-for="user in users" :key="users.indexOf(user)">
<v-card
v-if="user.creditList[year] && isFiltered(user)"
style="margin-top: 3px"
:loading="user.loading"
>
<v-card-title>
{{ user.lastname }}, {{ user.firstname }}
</v-card-title>
<Table v-bind:user="user" v-bind:year="year" />
<v-container fluid>
<v-row align="start" align-content="start">
<v-col>
<v-row>
<v-col>
<v-label>Vorjahr:</v-label>
</v-col>
<v-col>
<v-chip
outlined
:text-color="getLastColor(user.creditList[year][1].last)"
>{{
(user.creditList[year][1].last / 100).toFixed(2)
}}</v-chip
>
</v-col>
</v-row>
<v-row>
<v-col>
<v-label>Gesamt:</v-label>
</v-col>
<v-col>
<v-chip
outlined
x-large
:text-color="
getLastColor(
getAllSum(
user.creditList[year][2].sum,
user.creditList[year][1].last
)
)
"
>
{{
(
getAllSum(
user.creditList[year][2].sum,
user.creditList[year][1].last
) / 100
).toFixed(2)
}}
</v-chip>
</v-col>
</v-row>
</v-col>
<v-col align-self="center">
<v-row>
<v-col>
<v-label>Status:</v-label>
</v-col>
<v-col>
<v-chip outlined :text-color="getLockedColor(user.locked)">{{
user.locked ? 'Gesperrt' : 'nicht Gesperrt'
}}</v-chip>
</v-col>
</v-row>
<v-card outlined>
<v-row>
<v-card-title class="subtitle-2"
>Geld transferieren</v-card-title
>
<v-spacer />
<v-btn
text
icon
style="margin-right: 5px"
@click="setExpand(user)"
>
<v-icon :class="isExpand(user.expand)" dense
>$expand</v-icon
>
</v-btn>
</v-row>
<v-expand-transition>
<v-card-text v-show="user.expand">
<v-form style="margin-left: 15px; margin-right: 15px">
<v-row>
<v-col>
<v-text-field
:rules="[isNumber]"
label="Betrag"
v-model="amount"
></v-text-field>
</v-col>
<v-col>
<v-select
return-object
v-model="type"
label="Typ"
:items="[
{ value: 'amount', text: 'Schulden' },
{ value: 'credit', text: 'Guthaben' }
]"
item-text="text"
item-value="value"
></v-select>
</v-col>
</v-row>
<v-row>
<v-col>
<v-select
return-object
v-model="selectedYear"
label="Jahr"
:items="years"
item-text="text"
item-value="value"
></v-select>
</v-col>
<v-col>
<v-select
return-object
v-model="selectedMonth"
label="Monat"
:items="months"
item-text="text"
item-value="value"
></v-select>
</v-col>
</v-row>
</v-form>
<v-btn block @click="add(user)">Hinzufügen</v-btn>
</v-card-text>
</v-expand-transition>
</v-card>
</v-col>
</v-row>
</v-container>
</v-card>
</div>
</v-content>
</template>
<script>
import Table from './Table'
import { mapGetters, mapActions } from 'vuex'
import TableSkeleton from './Skeleton/TableSkeleton'
import {mdiChevronLeft, mdiChevronRight, mdiAccountSearch} from '@mdi/js'
export default {
name: 'Overview',
components: { TableSkeleton, Table },
props: {},
data() {
return {
keyboard_arrow_left: mdiChevronLeft,
keyboard_arrow_right: mdiChevronRight,
search_person: mdiAccountSearch,
errorExpand: false,
filter: '',
user: null,
amount: null,
isNumber: value => !isNaN(value) || 'Betrag muss eine Zahl sein.',
type: { value: 'credit', text: 'Guthaben' },
selectedYear: {
value: new Date().getFullYear(),
text: new Date().getFullYear()
},
selectedMonth: {
value: new Date().getMonth() + 1,
text: [
'Januar',
'Februar',
'März',
'April',
'Mai',
'Juni',
'Juli',
'August',
'September',
'Oktober',
'November',
'Dezember'
][new Date().getMonth()]
}
}
},
created() {},
methods: {
...mapActions({
createYears: 'finanzerUsers/createYears',
addAmount: 'finanzerUsers/addAmount',
addCredit: 'finanzerUsers/addCredit',
countYear: 'finanzerUsers/countYear',
sendMails: 'finanzerUsers/sendMails',
addUser: 'finanzerUsers/addUser'
}),
async getData(promise) {
return await promise
},
getLastColor(value) {
return value < 0 ? 'red' : 'green'
},
getAllSum(sum, lastYear) {
return lastYear + sum
},
getLockedColor(value) {
return value ? 'red' : 'green'
},
computeError(error) {
if (error) return 'error'
else return 'success'
},
errorMessage(error) {
if (error.error)
return (
'Konnte Email an ' +
error.user.firstname +
' ' +
error.user.lastname +
' nicht senden!'
)
else
return (
'Email wurde an ' +
error.user.firstname +
' ' +
error.user.lastname +
' versandt.'
)
},
setExpand(user) {
user.expand ? (user.expand = false) : (user.expand = true)
},
isExpand(value) {
return value ? 'rotate' : ''
},
// eslint-disable-next-line no-unused-vars
add(user) {
if (this.type.value === 'amount') {
this.addAmount({
user: user,
amount: this.amount,
year: this.selectedYear.value,
month: this.selectedMonth.value
})
}
if (this.type.value === 'credit') {
this.addCredit({
user: user,
credit: this.amount,
year: this.selectedYear.value,
month: this.selectedMonth.value
})
}
this.createDefault(
this.amount,
this.type,
this.selectedYear,
this.selectedMonth
)
},
isFiltered(user) {
try {
var filters = this.filter.split(' ')
for (var filter in filters) {
if (
user.firstname.toLowerCase().includes(filters[filter].toLowerCase()) ||
user.lastname.toLowerCase().includes(filters[filter].toLowerCase())
) {
return true
}
}
return false
} catch (e) {
return true
}
},
addToUser(user) {
this.addUser(user)
this.$router.push({name: 'activeUser', params: {id: user.username}})
},
createDefault() {
this.amount = null
this.type = { value: 'credit', text: 'Guthaben' }
this.selectedYear = {
value: new Date().getFullYear(),
text: new Date().getFullYear()
}
this.selectedMonth = {
value: new Date().getMonth() + 1,
text: [
'Januar',
'Februar',
'März',
'April',
'Mai',
'Juni',
'Juli',
'August',
'September',
'Oktober',
'November',
'Dezember'
][new Date().getMonth()]
}
}
},
computed: {
isActualYear() {
return this.year === new Date().getFullYear()
},
...mapGetters({
users: 'finanzerUsers/users',
allUsers: 'finanzerUsers/allUsers',
errorMails: 'finanzerUsers/errorMails',
year: 'finanzerUsers/year',
years: 'finanzerUsers/years',
months: 'finanzerUsers/months',
loading: 'finanzerUsers/usersLoading',
allUsersLoading: 'finanzerUsers/allUsersLoading'
})
}
}
</script>
<style scoped>
.rotate {
transform: rotate(180deg);
}
</style>

View File

@ -1,60 +0,0 @@
<template>
<div>
<v-card style="margin-top: 3px">
<v-card-title>
<v-skeleton-loader type="heading" />
</v-card-title>
<v-container>
<v-skeleton-loader type="table-thead"/>
<v-skeleton-loader type="table-row-divider@3"/>
</v-container>
<v-container fluid>
<v-row align="start" align-content="start">
<v-col>
<v-row>
<v-col>
<v-skeleton-loader type="chip" />
</v-col>
<v-col>
<v-skeleton-loader type="chip" />
</v-col>
</v-row>
<v-row>
<v-col>
<v-skeleton-loader type="chip"/>
</v-col>
<v-col>
<v-skeleton-loader type="chip" />
</v-col>
</v-row>
</v-col>
<v-col align-self="center">
<v-row>
<v-col>
<v-skeleton-loader type="chip" />
</v-col>
<v-col>
<v-skeleton-loader type="chip" />
</v-col>
</v-row>
<v-card outlined>
<v-row>
<v-card-title>
<v-skeleton-loader style="margin: 3px; margin-left: 10px" type="chip"/>
</v-card-title>
</v-row>
</v-card>
</v-col>
</v-row>
</v-container>
</v-card>
</div>
</template>
<script>
export default {
name: 'TableSkeleton'
}
</script>
<style scoped></style>

View File

@ -1,82 +0,0 @@
<template>
<div>
<v-toolbar tile>
<v-toolbar-title>
<v-row>
<v-col>
<v-skeleton-loader type="chip" />
</v-col>
<v-col>
<v-skeleton-loader type="chip" />
</v-col>
</v-row>
</v-toolbar-title>
<v-spacer />
<v-toolbar-items>
<v-skeleton-loader type="button" />
</v-toolbar-items>
</v-toolbar>
<v-card style="margin-top: 3px;">
<v-card-title><v-skeleton-loader type="heading"/></v-card-title>
<v-card-text>
<v-form style="margin-left: 15px; margin-right: 15px">
<v-row>
<v-col>
<v-skeleton-loader type="chip" />
</v-col>
<v-col>
<v-skeleton-loader type="chip" />
</v-col>
<v-col>
<v-skeleton-loader type="button" />
</v-col>
</v-row>
<v-divider style="margin-bottom: 15px;" />
<v-row>
<v-col>
<v-skeleton-loader type="chip" />
</v-col>
<v-col>
<v-skeleton-loader type="chip" />
</v-col>
</v-row>
<v-row>
<v-skeleton-loader type="button" />
</v-row>
</v-form>
</v-card-text>
</v-card>
<v-card style="margin-top: 3px;">
<v-card-title><v-skeleton-loader type="chip"/></v-card-title>
<v-card-text>
<v-form style="margin-left: 15px; margin-right: 15px">
<v-row>
<v-col>
<v-skeleton-loader type="chip" />
</v-col>
<v-col>
<v-skeleton-loader type="chip" />
</v-col>
</v-row>
<v-row>
<v-col>
<v-skeleton-loader type="chip" />
</v-col>
<v-col>
<v-skeleton-loader type="chip" />
</v-col>
</v-row>
</v-form>
<v-skeleton-loader type="button" />
</v-card-text>
</v-card>
</div>
</template>
<script>
export default {
name: 'UserSkeleton'
}
</script>
<style scoped></style>

View File

@ -1,125 +0,0 @@
<template>
<v-data-table dense :headers="headers" :items="user.creditList[year]" :hide-default-footer="true">
<template v-slot:item.jan_amount="{ item }">
<v-chip outlined :text-color="getColor(item, item.jan_amount)">
{{(item.jan_amount /
100).toFixed(2)}}
</v-chip>
</template>
<template v-slot:item.feb_amount="{ item }">
<v-chip outlined :text-color="getColor(item, item.feb_amount)">
{{(item.feb_amount /
100).toFixed(2)}}
</v-chip>
</template>
<template v-slot:item.maer_amount="{ item }">
<v-chip outlined :text-color="getColor(item, item.maer_amount)">
{{(item.maer_amount /
100).toFixed(2)}}
</v-chip>
</template>
<template v-slot:item.apr_amount="{ item }">
<v-chip outlined :text-color="getColor(item, item.apr_amount)">
{{(item.apr_amount /
100).toFixed(2)}}
</v-chip>
</template>
<template v-slot:item.mai_amount="{ item }">
<v-chip outlined :text-color="getColor(item, item.mai_amount)">
{{(item.mai_amount /
100).toFixed(2)}}
</v-chip>
</template>
<template v-slot:item.jun_amount="{ item }">
<v-chip outlined :text-color="getColor(item, item.jun_amount)">
{{(item.jun_amount /
100).toFixed(2)}}
</v-chip>
</template>
<template v-slot:item.jul_amount="{ item }">
<v-chip outlined :text-color="getColor(item, item.jul_amount)">
{{(item.jul_amount /
100).toFixed(2)}}
</v-chip>
</template>
<template v-slot:item.aug_amount="{ item }">
<v-chip outlined :text-color="getColor(item, item.aug_amount)">
{{(item.aug_amount /
100).toFixed(2)}}
</v-chip>
</template>
<template v-slot:item.sep_amount="{ item }">
<v-chip outlined :text-color="getColor(item, item.sep_amount)">
{{(item.sep_amount /
100).toFixed(2)}}
</v-chip>
</template>
<template v-slot:item.okt_amount="{ item }">
<v-chip outlined :text-color="getColor(item, item.okt_amount)">
{{(item.okt_amount /
100).toFixed(2)}}
</v-chip>
</template>
<template v-slot:item.nov_amount="{ item }">
<v-chip outlined :text-color="getColor(item, item.nov_amount)">
{{(item.nov_amount /
100).toFixed(2)}}
</v-chip>
</template>
<template v-slot:item.dez_amount="{ item }">
<v-chip outlined :text-color="getColor(item, item.dez_amount)">
{{(item.dez_amount /
100).toFixed(2)}}
</v-chip>
</template>
<template v-slot:item.sum="{ item }">
<v-chip outlined :text-color="getColor(item, item.sum)">{{(item.sum / 100).toFixed(2)}}</v-chip>
</template>
</v-data-table>
</template>
<script>
export default {
name: 'Table',
props: {
user: Object,
year: Number
},
data() {
return {
headers: [
{
text: 'Schulden / Guthaben',
align: 'left',
sortable: false,
value: 'type'
},
{ text: 'Januar in EUR', value: 'jan_amount' },
{ text: 'Februar in EUR', value: 'feb_amount' },
{ text: 'März in EUR', value: 'maer_amount' },
{ text: 'April in EUR', value: 'apr_amount' },
{ text: 'Mai in EUR', value: 'mai_amount' },
{ text: 'Juni in EUR', value: 'jun_amount' },
{ text: 'Juli in EUR', value: 'jul_amount' },
{ text: 'August in EUR', value: 'aug_amount' },
{ text: 'September in EUR', value: 'sep_amount' },
{ text: 'Oktober in EUR', value: 'okt_amount' },
{ text: 'November in EUR', value: 'nov_amount' },
{ text: 'Dezember in EUR', value: 'dez_amount' },
{ text: 'Summe in EUR', value: 'sum' }
]
}
},
methods: {
getColor(item, value) {
if (item.type === 'Summe') {
return value < 0 ? 'red' : 'green'
}
return item.type === 'Guthaben' ? 'green' : 'red'
}
}
}
</script>
<style scoped>
</style>

View File

@ -1,363 +0,0 @@
<template>
<div>
<v-content v-if="loading" >
<UserSkeleton />
</v-content>
<v-content v-if="activeUser">
<v-toolbar tile>
<v-toolbar-title
>{{ activeUser.lastname }},
{{ activeUser.firstname }}</v-toolbar-title
>
<v-spacer />
<v-toolbar-items>
<v-btn @click="sendMail({ username: activeUser.username })" text
>Email senden</v-btn
>
</v-toolbar-items>
</v-toolbar>
<v-progress-linear v-if="activeUser.loading" indeterminate />
<v-expand-transition>
<v-card style="margin-top: 3px" v-show="errorMail">
<v-alert dense :type="computeError(errorMail)">
{{ errorMessage(errorMail) }}
</v-alert>
</v-card>
</v-expand-transition>
<v-card style="margin-top: 3px;">
<v-card-title>Konfiguration</v-card-title>
<v-card-text>
<v-form style="margin-left: 15px; margin-right: 15px">
<v-row>
<v-col>
<v-label>Status:</v-label>
</v-col>
<v-col>
<v-chip outlined :text-color="getLockedColor(activeUser.locked)"
>{{ activeUser.locked ? 'Gesperrt' : 'nicht Gesperrt' }}
</v-chip>
</v-col>
<v-col>
<v-btn
@click="
doLock({ user: activeUser, locked: !activeUser.locked })
"
>{{ activeUser.locked ? 'Entperren' : 'Sperren' }}
</v-btn>
</v-col>
</v-row>
<v-divider style="margin-bottom: 15px;" />
<v-row>
<v-col>
<v-text-field
:rules="[isNumber]"
label="Betrag des Sperrlimits in € (EURO)"
v-model="limit"
></v-text-field>
</v-col>
<v-col>
<v-select
return-object
v-model="autoLock"
label="Automatische Sperre"
:items="[
{ value: true, text: 'Aktiviert' },
{ value: false, text: 'Deaktiviert' }
]"
item-text="text"
item-value="value"
/>
</v-col>
</v-row>
<v-row>
<v-btn
block
@click="
saveConfig({
user: activeUser,
limit: limit,
autoLock: autoLock.value
})
"
>Speichern
</v-btn>
</v-row>
</v-form>
</v-card-text>
</v-card>
<v-card style="margin-top: 3px;">
<v-card-title>Geld transferieren</v-card-title>
<v-card-text>
<v-form style="margin-left: 15px; margin-right: 15px">
<v-row>
<v-col>
<v-text-field
:rules="[isNumber]"
label="Betrag"
v-model="amount"
></v-text-field>
</v-col>
<v-col>
<v-select
return-object
v-model="type"
label="Typ"
:items="[
{ value: 'amount', text: 'Schulden' },
{ value: 'credit', text: 'Guthaben' }
]"
item-text="text"
item-value="value"
></v-select>
</v-col>
</v-row>
<v-row>
<v-col>
<v-select
return-object
v-model="selectedYear"
label="Jahr"
:items="selectYears"
item-text="text"
item-value="value"
></v-select>
</v-col>
<v-col>
<v-select
return-object
v-model="selectedMonth"
label="Monat"
:items="months"
item-text="text"
item-value="value"
></v-select>
</v-col>
</v-row>
</v-form>
<v-btn block @click="add">Hinzufügen</v-btn>
</v-card-text>
</v-card>
<div v-for="year in years" :key="years.indexOf(year)">
<v-card style="margin-top: 3px;">
<v-card-title>{{ year }}</v-card-title>
<Table v-bind:user="activeUser" v-bind:year="year" />
<v-container fluid>
<v-col>
<v-row>
<v-col>
<v-label>Vorjahr:</v-label>
</v-col>
<v-col>
<v-chip
outlined
:text-color="
getLastColor(activeUser.creditList[year][1].last)
"
>
{{ (activeUser.creditList[year][1].last / 100).toFixed(2) }}
</v-chip>
</v-col>
<v-col>
<v-label>Gesamt:</v-label>
</v-col>
<v-col>
<v-chip
outlined
x-large
:text-color="
getLastColor(
getAllSum(
activeUser.creditList[year][2].sum,
activeUser.creditList[year][1].last
)
)
"
>
{{
(
getAllSum(
activeUser.creditList[year][2].sum,
activeUser.creditList[year][1].last
) / 100
).toFixed(2)
}}
</v-chip>
</v-col>
</v-row>
</v-col>
</v-container>
</v-card>
</div>
</v-content>
</div>
</template>
<script>
import Table from './Table'
import { mapGetters, mapActions } from 'vuex'
import UserSkeleton from "./Skeleton/UserSkeleton";
export default {
name: 'User',
props: {
id: String
},
components: {UserSkeleton, Table },
data() {
return {
isNumber: value => !isNaN(value) || 'Betrag muss eine Zahl sein.',
limit: null,
autoLock: null,
amount: null,
type: { value: 'credit', text: 'Guthaben' },
selectedYear: {
value: new Date().getFullYear(),
text: new Date().getFullYear()
},
selectedMonth: {
value: new Date().getMonth() + 1,
text: [
'Januar',
'Februar',
'März',
'April',
'Mai',
'Juni',
'Juli',
'August',
'September',
'Oktober',
'November',
'Dezember'
][new Date().getMonth()]
}
}
},
created() {
this.setActiveUser(this.$route.params.id)
},
methods: {
...mapActions({
addAmount: 'finanzerUsers/addAmount',
addCredit: 'finanzerUsers/addCredit',
sendMail: 'finanzerUsers/sendMail',
doLock: 'finanzerUsers/doLock',
saveConfig: 'finanzerUsers/saveConfig',
setActiveUser: 'finanzerUsers/setActiveUser'
}),
getLastColor(value) {
return value < 0 ? 'red' : 'green'
},
getAllSum(sum, lastYear) {
return lastYear + sum
},
getLockedColor(value) {
return value ? 'red' : 'green'
},
add() {
if (this.type.value === 'amount') {
this.addAmount({
user: this.activeUser,
amount: this.amount,
year: this.selectedYear.value,
month: this.selectedMonth.value
})
}
if (this.type.value === 'credit') {
this.addCredit({
user: this.activeUser,
credit: this.amount,
year: this.selectedYear.value,
month: this.selectedMonth.value
})
}
this.createDefault()
},
createDefault() {
// eslint-disable-next-line no-unused-vars
let year = new Date().getFullYear()
// eslint-disable-next-line no-unused-vars
let month = new Date().getMonth()
this.amount = null
this.type = { value: 'credit', text: 'Guthaben' }
this.selectedYear = {
value: new Date().getFullYear(),
text: new Date().getFullYear()
}
this.selectedMonth = {
value: new Date().getMonth() + 1,
text: [
'Januar',
'Februar',
'März',
'April',
'Mai',
'Juni',
'Juli',
'August',
'September',
'Oktober',
'November',
'Dezember'
][new Date().getMonth()]
}
},
computeError(error) {
if (error) {
if (error.error) return 'error'
else return 'success'
}
},
errorMessage(error) {
if (error) {
if (error.error)
return (
'Konnte Email an ' +
error.user.firstname +
' ' +
error.user.lastname +
' nicht senden!'
)
else
return (
'Email wurde an ' +
error.user.firstname +
' ' +
error.user.lastname +
' versandt.'
)
}
}
},
computed: {
years() {
let years = []
for (let year in this.activeUser.creditList) {
years.unshift(parseInt(year))
}
return years
},
...mapGetters({
activeUser: 'finanzerUsers/activeUser',
errorMail: 'finanzerUsers/errorMail',
months: 'finanzerUsers/months',
selectYears: 'finanzerUsers/selectYears',
loading: 'finanzerUsers/addUserLoading'
})
},
watch: {
activeUser(newVal) {
this.limit = (newVal.limit / 100).toFixed(2)
this.autoLock = {
value: newVal.autoLock,
text: newVal.autoLock ? 'Aktiviert' : 'Deaktiviert'
}
},
id(newVal) {
this.setActiveUser(newVal)
}
}
}
</script>
<style scoped></style>

View File

@ -1,28 +0,0 @@
<template>
<v-list>
<v-list-item class="title" link :to="{name: 'gastroPricelist'}">
<v-list-item-icon>
<v-icon>{{list}}</v-icon>
</v-list-item-icon>
<v-list-item-title>
Preisliste
</v-list-item-title>
</v-list-item>
</v-list>
</template>
<script>
import { mdiFileMultiple } from '@mdi/js'
export default {
name: "GastroNavigation",
data() {
return {
list: mdiFileMultiple
}
}
}
</script>
<style scoped>
</style>

8
src/components/models.ts Normal file
View File

@ -0,0 +1,8 @@
export interface Todo {
id: number;
content: string;
}
export interface Meta {
totalCount: number;
}

View File

@ -1,497 +0,0 @@
<template>
<div>
<v-data-table
:headers="headers"
:items="priceList"
:search="search"
:loading="priceListLoading || typesLoading"
>
<template v-slot:top>
<v-toolbar flat color="white">
<v-toolbar-title>Preisliste</v-toolbar-title>
<v-spacer></v-spacer>
<v-text-field
v-model="search"
label="Suche Getränk"
single-line
hide-details
>
<template v-slot:append>
<v-icon>{{ searchIcon }}</v-icon>
</template>
</v-text-field>
<v-dialog v-model="dialog" v-if="isGastro && isGastroPage">
<template v-slot:activator="{ on }">
<v-btn
fab
x-small
color="primary"
class="mb-2"
v-on="on"
style="margin: 5px"
>
<v-icon>{{ plus }}</v-icon>
</v-btn>
</template>
<v-card>
<v-card-title>
<span class="headline">{{ formTitle }}</span>
</v-card-title>
<v-card-text>
<v-container>
<v-row>
<v-col cols="12" sm="6" md="4">
<v-text-field
v-model="editedItem.name"
label="Name"
outlined
></v-text-field>
</v-col>
<v-col cols="12" sm="6" md="4">
<v-autocomplete
return-object
v-model="editedItem.type"
label="Kategorie"
item-text="name"
item-value="id"
:items="types"
outlined
:search-input.sync="searchType"
no-data-text="Kategorie nicht vorhanden."
>
<template v-slot:append-item>
<v-list-item v-if="!inType(searchType)">
<v-list-item-title>
{{ searchType }}
</v-list-item-title>
<v-btn
dark
x-small
color="blue darken-1"
@click="addType()"
:disabled="inType(searchType)"
fab
>
<v-icon>
{{ plus }}
</v-icon>
</v-btn>
</v-list-item>
</template>
</v-autocomplete>
</v-col>
<v-col cols="12" sm="6" md="4">
<v-text-field
v-model="editedItem.price"
label="Preis in €"
outlined
></v-text-field>
</v-col>
<v-col cols="12" sm="6" md="4">
<v-text-field
v-model="editedItem.price_big"
label="Preis groß in €"
outlined
></v-text-field>
</v-col>
<v-col cols="12" sm="6" md="4">
<v-text-field
v-model="editedItem.price_club"
label="Preis Club in €"
outlined
></v-text-field>
</v-col>
<v-col cols="12" sm="6" md="4">
<v-text-field
v-model="editedItem.price_club_big"
label="Preis Club groß in €"
outlined
></v-text-field>
</v-col>
<v-col col="12" sm="6" md="4">
<v-text-field
v-model="editedItem.premium"
label="Aufpreis in €"
outlined
/>
</v-col>
<v-col cols="12" sm="6" md="4">
<v-text-field
v-model="editedItem.premium_club"
label="Aufpreis Club in €"
outlined
/>
</v-col>
<v-col cols="12" sm="6" md="4">
<v-text-field
v-model="editedItem.price_extern_club"
label="Preis extern Club in €"
outlined
/>
</v-col>
</v-row>
</v-container>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="blue darken-1" text @click="close"
>Abbrechen</v-btn
>
<v-btn color="blue darken-1" text @click="save"
>Speichern</v-btn
>
</v-card-actions>
</v-card>
</v-dialog>
</v-toolbar>
</template>
<template v-slot:item.type="{ item }">
{{ computeType(item.type) }}
</template>
<template v-slot:item.price="{ item }">
{{ item.price ? (item.price / 100).toFixed(2) : '' }}
</template>
<template v-slot:item.price_big="{ item }">
{{ item.price_big ? (item.price_big / 100).toFixed(2) : '' }}
</template>
<template v-slot:item.price_club="{ item }">
{{
item.name.toLowerCase() == 'long island ice tea'.toLowerCase()
? 'Ein Klubmitglied bestellt keinen Long Island Icetea'
: item.price_club
? (item.price_club / 100).toFixed(2)
: ''
}}
</template>
<template v-slot:item.price_club_big="{ item }">
{{ item.price_club_big ? (item.price_club_big / 100).toFixed(2) : '' }}
</template>
<template v-slot:item.premium="{ item }">
{{ item.premium ? (item.premium / 100).toFixed(2) : '' }}
</template>
<template v-slot:item.premium_club="{ item }">
{{ item.premium_club ? (item.premium_club / 100).toFixed(2) : '' }}
</template>
<template v-slot:item.price_extern_club="{ item }">
{{
item.price_extern_club
? (item.price_extern_club / 100).toFixed(2)
: ''
}}
</template>
<template v-slot:item.action="{ item }">
<v-icon small class="mr-2" @click="editItem(item)">
{{ editIcon }}
</v-icon>
<v-icon small @click="deleteItem(item)">
{{ deleteIcon }}
</v-icon>
</template>
</v-data-table>
<v-card tile v-if="isGastro && isGastroPage" :loading="typesLoading">
<v-card-title>
Kategorien
<v-spacer />
<v-btn fab x-small @click="dialogType = true" color="primary">
<v-icon>{{ plus }}</v-icon>
</v-btn>
</v-card-title>
<v-card-text>
<div tile v-for="type in types" :key="type.id">
<v-card tile>
<v-card-text class="black--text">
<v-row class="ml-3 mr-3">
{{ type.name }}
<v-spacer />
<v-btn icon @click="editType(type)">
<v-icon>
{{ editIcon }}
</v-icon>
</v-btn>
<v-btn icon @click="deleteType(type)">
<v-icon>
{{ deleteIcon }}
</v-icon>
</v-btn>
</v-row>
</v-card-text>
</v-card>
</div>
</v-card-text>
<v-dialog v-model="dialogType">
<v-card>
<v-card-title>
{{ dialogTypeTitle }}
</v-card-title>
<v-card-text>
<v-container>
<v-text-field
v-model="editedType.name"
outlined
label="Name der Kategorie"
/>
</v-container>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn text color="blue darken-1" @click="closeType()">
Abbrechen
</v-btn>
<v-btn
text
color="blue darken-1"
@click="saveType()"
:disabled="inType(editedType.name)"
>
Speichern
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-card>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex'
import { mdiMagnify, mdiPlus, mdiPencil, mdiDelete } from '@mdi/js'
export default {
name: 'PriceList',
data() {
return {
editIcon: mdiPencil,
deleteIcon: mdiDelete,
searchIcon: mdiMagnify,
plus: mdiPlus,
searchType: null,
search: null,
dialog: null,
dialogType: null,
editedIndex: -1,
headers: [
{
text: 'Name',
value: 'name'
},
{
text: 'Kategorie',
value: 'type'
},
{
text: 'Preis in €',
value: 'price'
},
{
text: 'Preis groß in €',
value: 'price_big'
},
{
text: 'Preis Club in €',
value: 'price_club'
},
{
text: 'Preis groß Club in €',
value: 'price_club_big'
},
{
text: 'Aufpreis in €',
value: 'premium'
},
{
text: 'Aufpreis Club in €',
value: 'premium_club'
},
{
text: 'Preis Club extern in €',
value: 'price_extern_club'
}
],
editedItem: {
name: null,
type: { id: -1, name: null },
price: null,
price_big: null,
price_club: null,
price_club_big: null,
premium: null,
premium_club: null,
price_extern_club: null
},
defaultItem: {
name: null,
type: { id: -1, name: null },
price: null,
price_big: null,
price_club: null,
price_club_big: null,
premium: null,
premium_club: null,
price_extern_club: null
},
editedType: {
id: -1,
name: null
},
defaultType: {
id: -1,
name: null
}
}
},
methods: {
...mapActions({
getPriceList: 'priceList/getPriceList',
getTypes: 'priceList/getTypes',
setDrink: 'priceList/setDrink',
updateDrink: 'priceList/updateDrink',
deleteDrink: 'priceList/deleteDrink',
setDrinkType: 'priceList/setDrinkType',
updateDrinkType: 'priceList/updateDrinkType',
deleteDrinkType: 'priceList/deleteDrinkType'
}),
editType(item) {
this.editedType = Object.assign({}, item)
this.dialogType = true
},
closeType() {
this.dialogType = false
setTimeout(() => {
this.editedType = Object.assign({}, this.defaultType)
}, 300)
},
saveType() {
this.editedType.id === -1
? this.setDrinkType(this.editedType)
: this.updateDrinkType(this.editedType)
this.closeType()
},
deleteType(item) {
confirm('Bist du sicher, dass du diese Kategorie entfernen willst?') &&
this.deleteDrinkType({ id: item.id })
},
addType() {
this.setDrinkType({ name: this.searchType })
},
close() {
this.dialog = false
setTimeout(() => {
this.editedItem = Object.assign({}, this.defaultItem)
this.editedIndex = -1
}, 300)
},
editItem(item) {
this.editedIndex = item.id
this.editedItem = Object.assign({}, item)
for (let i in this.editedItem) {
this.editedItem[i] = isNaN(this.editedItem[i])
? this.editedItem[i]
: this.editedItem[i] == null || this.editedItem[i] == 0
? null
: (this.editedItem[i] / 100).toFixed(2)
}
this.editedItem.type = Object.assign(
{},
this.types.find(a => a.id == item.type)
)
this.dialog = true
},
deleteItem(item) {
confirm('Bist du sicher, dass die dieses Getränk entfernen wills?') &&
this.deleteDrink({ id: item.id })
},
save() {
var drink = {
id: this.editedIndex,
name: this.editedItem.name,
type: this.editedItem.type.id,
price: !isNaN(this.editedItem.price)
? this.editedItem.price * 100
: null,
price_big: !isNaN(this.editedItem.price_big)
? this.editedItem.price_big * 100
: null,
price_club: !isNaN(this.editedItem.price_club)
? this.editedItem.price_club * 100
: null,
price_club_big: !isNaN(this.editedItem.price_club_big)
? this.editedItem.price_club_big * 100
: null,
premium: !isNaN(this.editedItem.premium)
? this.editedItem.premium * 100
: null,
premium_club: !isNaN(this.editedItem.premium_club)
? this.editedItem.premium_club * 100
: null,
price_extern_club: !isNaN(this.editedItem.price_extern_club)
? this.editedItem.price_extern_club * 100
: null
}
drink.id === -1 ? this.setDrink(drink) : this.updateDrink(drink)
this.editedItem = Object.assign({}, this.defaultItem)
this.close()
}
},
computed: {
...mapGetters({
priceList: 'priceList/priceList',
types: 'priceList/types',
priceListLoading: 'priceList/priceListLoading',
typesLoading: 'priceList/typesLoading',
isGastro: 'isGastro'
}),
isGastroPage() {
return this.$route.name === 'gastroPricelist'
},
formTitle() {
return this.editedIndex === -1 ? 'Neues Getränk' : 'Bearbeite Getränk'
},
inType() {
return text => {
return !!this.types.find(a => {
if (a.name === null || text === null) {
return true
} else {
return a.name.toLowerCase() === text.toLowerCase()
}
})
}
},
dialogTypeTitle() {
return this.editedType.id === -1
? 'Neue Kategorie'
: 'Bearbeite Kategorie'
},
computeType() {
return id => {
const type = this.types.find(a => {
return a.id === id
})
return type ? type.name : null
}
}
},
created() {
this.getPriceList()
this.getTypes()
if (this.isGastro && this.isGastroPage) {
this.headers.push({
text: 'Aktion',
value: 'action',
sortable: false,
filterable: false
})
}
console.log(this.$route)
},
watch: {
dialog(val) {
val || this.close()
}
}
}
</script>
<style scoped></style>

View File

@ -1,497 +0,0 @@
<template>
<v-container>
<v-dialog v-model="checkValidate" max-width="290">
<v-card>
<v-card-title>
Willst du wirklich??
</v-card-title>
<v-card-text v-if="stornoMessage">
Willst du wirklich den Betrag
{{ (stornoMessage.amount / 100).toFixed(2) }} von
{{ stornoMessage.user.firstname }}
{{ stornoMessage.user.lastname }} stornieren?
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn text @click="cancelStorno">Abbrechen</v-btn>
<v-btn text @click="acceptStorno">Stornieren</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<v-dialog v-model="dialog" max-width="290">
<v-card>
<v-card-title class="headline"
>Transaktion ist länger als 15 Sekunden her!</v-card-title
>
<v-card-text>
Da die Transaktion länger als 15 Sekunden her ist, kann eine
Stornierung nicht durchgeführt werden. Wende dich bitte an den
Finanzer.
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn text @click="dialog = false">
Verstanden
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<v-dialog
v-if="overLimitUser"
v-model="overLimitUser"
max-width="290"
persistent
>
<v-card>
<v-card-title>Warnung</v-card-title>
<v-card-text>
{{ overLimitUser.firstname }} {{ overLimitUser.lastname }} übersteigt
das Anschreibelimit von
{{ (overLimitUser.limit / 100).toFixed(2) }} . Danach kann dieses
Mitglied nichts mehr anschreiben. Will er das wirklich?
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn text @click="cancel()">Abbrechen</v-btn>
<v-btn text @click="continueAdd(overLimitUser)">Anschreiben</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<v-dialog v-if="overOverLimit" v-model="overOverLimit" max-width="290" persistent>
<v-card>
<v-card-title>Anschreiben nicht möglich</v-card-title>
<v-card-text>
{{ overOverLimit.firstname }}
{{ overOverLimit.lastname }} überschreitet das Anschreibelimit zuviel.
Das Anschreiben wurde daher gestoppt und zurückgesetzt.
</v-card-text>
<v-card-actions>
<v-btn text @click="overOverLimit = null">Verstanden</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<AddAmountSkeleton v-if="loading" />
<v-navigation-drawer v-model="menu" right app clipped>
<v-list-item-group :key="componentRenderer">
<v-list-item inactive>
<v-list-item-title class="headline">
Verlauf
</v-list-item-title>
</v-list-item>
<v-divider />
<div
v-for="message in messages"
three-line
:key="messages.indexOf(message)"
>
<div v-if="message">
<v-list-item three-line inactive @click="storno(message)">
<v-list-item-content>
<v-progress-linear indeterminate v-if="message.loading" />
<v-list-item-title>{{ now(message.date) }}</v-list-item-title>
<v-list-item-subtitle>
{{ createSum(message) }} {{ createMessage(message) }}
</v-list-item-subtitle>
<v-list-item-subtitle class="red--text" v-if="message.storno"
>STORNIERT!!!
</v-list-item-subtitle>
<v-list-item-subtitle class="red--text" v-else-if="message.error">
ERROR!
</v-list-item-subtitle>
<v-list-item-action-text v-if="under5minutes(message.date) && !message.error"
>Klicken um zu Stornieren
</v-list-item-action-text>
</v-list-item-content>
</v-list-item>
</div>
</div>
</v-list-item-group>
</v-navigation-drawer>
<v-card v-if="!loading" :loading="addLoading">
<v-card-title>
{{ user.firstname }} {{ user.lastname }}
<v-spacer />
<v-btn @click="menu = !menu" icon>
<v-icon>{{ menuIcon }}</v-icon>
</v-btn>
</v-card-title>
<v-card-subtitle v-if="user.limit + getAllSum() > 0">
Nur noch {{ ((user.limit + getAllSum()) / 100).toFixed(2) }}
übrig!!
</v-card-subtitle>
<v-card-text>
<v-row>
<v-col cols="10">
<v-row>
<v-col cols="6" sm="4">
<v-btn
class="creditBtn"
block
@click="addingAmount(200)"
:color="color"
:disabled="user.locked"
>2 </v-btn
>
</v-col>
<v-col cols="6" sm="4">
<v-btn
class="creditBtn"
block
@click="addingAmount(100)"
:color="color"
:disabled="user.locked"
>1 </v-btn
>
</v-col>
<v-col cols="6" sm="4">
<v-btn
class="creditBtn"
block
@click="addingAmount(50)"
:color="color"
:disabled="user.locked"
>0,50 </v-btn
>
</v-col>
<v-col cols="6" sm="4">
<v-btn
class="creditBtn"
block
@click="addingAmount(40)"
:color="color"
:disabled="user.locked"
>0,40 </v-btn
>
</v-col>
<v-col cols="6" sm="4">
<v-btn
class="creditBtn"
block
@click="addingAmount(20)"
:color="color"
:disabled="user.locked"
>0,20 </v-btn
>
</v-col>
<v-col cols="6" sm="4">
<v-btn
class="creditBtn"
block
@click="addingAmount(10)"
:color="color"
:disabled="user.locked"
>0,10 </v-btn
>
</v-col>
<v-col cols="8">
<v-text-field
outlined
type="number"
v-model="value"
label="Benutzerdefinierter Betrag"
:disabled="user.locked"
></v-text-field>
</v-col>
<v-col cols="4">
<v-btn
fab
:color="color"
@click="addAmountMore()"
:disabled="user.locked"
>
<v-icon>{{ plus }}</v-icon>
</v-btn>
</v-col>
</v-row>
</v-col>
<v-col align-self="center">
<v-row>
<v-list-item>
<v-list-item-content class="text-center">
<v-list-item-action-text :class="getColor(getAllSum())"
>{{ (getAllSum() / 100).toFixed(2) }}
</v-list-item-action-text>
<v-list-item-action-text v-if="toSetAmount">
- {{ (toSetAmount / 100).toFixed(2) }}
</v-list-item-action-text>
</v-list-item-content>
</v-list-item>
</v-row>
</v-col>
</v-row>
<v-alert v-if="user.locked" type="error"
>{{ user.firstname }} darf nicht mehr anschreiben.
{{ user.firstname }} sollte sich lieber mal beim Finanzer
melden.</v-alert
>
</v-card-text>
</v-card>
<v-snackbar
v-for="message in messages"
:key="messages.indexOf(message)"
:color="message.error ? 'error' : 'success'"
bottom
:timeout="0"
:multi-line="true"
v-model="message.visible"
vertical
>
<div class="title">
<p style="font-size: 5em; margin: 20px">{{ createSum(message) }}</p>
{{ createMessage(message) }}
</div>
<div>
{{ now(message.date) }}
</div>
<v-btn color="white" icon @click="message.visible = false">
<v-icon>
{{ close }}
</v-icon>
</v-btn>
</v-snackbar>
<ConnectionError/>
</v-container>
</template>
<script>
import { mapGetters, mapActions } from 'vuex'
// eslint-disable-next-line no-unused-vars
import { mdiMenu, mdiPlus, mdiClose } from '@mdi/js'
import AddAmountSkeleton from './Skeleton/AddAmountSkeleton'
import ConnectionError from "@/components/ConnectionError";
export default {
name: 'AddAmount',
components: {ConnectionError, AddAmountSkeleton },
data() {
return {
color: 'green accent-4',
value: null,
plus: mdiPlus,
menu: false,
dialog: false,
componentRenderer: 0,
timer: '',
menuIcon: mdiMenu,
close: mdiClose,
checkValidate: false,
stornoMessage: null,
timeout: null,
toSetAmount: null,
overLimitUser: null,
overOverLimit: null,
}
},
created() {
this.timer = setInterval(this.forceRender, 1000)
},
methods: {
...mapActions({
addAmount: 'user/addAmount',
commitStorno: 'user/storno'
}),
continueAdd(user) {
this.overLimitUser = null
user.checkedOverLimit = true
if (this.value) {
this.addAmount(Math.round(Math.abs(this.value * 100)))
setTimeout(() => {
this.value = null
this.toSetAmount = null
}, 300)
} else {
user.timeout = setTimeout(() => {
this.addAmount(this.toSetAmount)
setTimeout(() => {
this.toSetAmount = null
}, 300)
}, 2000)
}
},
cancel() {
this.toSetAmount = null
this.value = null
this.overLimitUser = null
},
checkOverLimitIsValid(user) {
if (this.toSetAmount && user.autoLock) {
if ((this.getAllSum() - Number.parseInt(this.toSetAmount)) < -(user.limit + 500)) {
this.overOverLimit = user
this.toSetAmount = null
this.value = null
return false
}
}
return true
},
checkOverLimit(user) {
if (this.toSetAmount) {
if (( this.getAllSum() - this.toSetAmount) < -user.limit) {
return user.checkedOverLimit ? false : true
}
}
return false
},
addingAmount(amount) {
clearTimeout(this.timeout)
this.toSetAmount = this.toSetAmount ? this.toSetAmount + amount : amount
if (this.checkOverLimitIsValid(this.user)) {
if (this.checkOverLimit(this.user) && this.user.autoLock) {
this.overLimitUser = this.user
} else {
this.timeout = setTimeout(() => {
this.addAmount(this.toSetAmount)
setTimeout(() => {
this.toSetAmount = null
}, 300)
}, 2000)
}
}
},
forceRender() {
this.componentRenderer += 1
},
getColor(value) {
return value >= 0 ? 'title green--text' : 'title red--text'
},
getAllSum() {
if (this.user)
return (
this.user.creditList[this.year][2].sum +
this.user.creditList[this.year][1].last
)
return 0
},
storno(message) {
if (!message.error) {
if (!this.under5minutes(message.date)) this.dialog = true
else {
this.checkValidate = true
this.stornoMessage = message
}
}
},
acceptStorno() {
this.commitStorno({
amount: this.stornoMessage.amount,
date: this.stornoMessage.date
})
setTimeout(() => {
this.cancelStorno()
}, 300)
},
cancelStorno() {
this.stornoMessage = null
this.checkValidate = null
},
addAmountMore() {
this.toSetAmount = this.toSetAmount
? this.toSetAmount + Math.round(Math.abs(this.value * 100))
: Math.round(Math.abs(this.value * 100))
if (this.checkOverLimitIsValid(this.user)) {
if (this.checkOverLimit(this.user) && this.user.autoLock) {
this.overLimitUser = this.user
}
else {
this.addAmount(Math.abs(this.value * 100))
setTimeout(() => {
this.value = null
}, 300)
}
}
},
createSum(message) {
var text = '' + (message.amount / 100).toFixed(2) + '€'
return text
},
createMessage(message) {
var text = ''
if (message.error) {
text =
' konnten nicht zu ' +
message.user.firstname +
' ' +
message.user.lastname +
' hinzufügen.'
} else {
text =
' wurde zu ' +
message.user.firstname +
' ' +
message.user.lastname +
' hinzugefügt.'
}
return text
}
},
computed: {
...mapGetters({
user: 'user/user',
year: 'user/year',
loading: 'user/loading',
addLoading: 'user/addLoading',
messages: 'user/messages'
}),
under5minutes() {
return now => {
var actual = new Date()
return actual - now < 15000
}
},
now() {
return now => {
var actual = new Date()
var zero = new Date(0)
var date = new Date(actual - now)
if (date.getFullYear() === zero.getFullYear()) {
if (date.getMonth() === zero.getMonth()) {
if (date.getDate() === zero.getDate()) {
if (date.getHours() === zero.getDate()) {
if (date.getMinutes() < 1) {
return 'vor ' + date.getSeconds() + ' Sekunden'
} else if (date.getMinutes() < 10) {
return 'vor ' + date.getMinutes() + ' Minuten'
} else {
return (
(now.getHours() < 10 ? '0' : '') +
now.getHours() +
':' +
(now.getMinutes() < 10 ? '0' : '') +
now.getMinutes()
)
}
} else {
return (
(now.getHours() < 10 ? '0' : '') +
now.getHours() +
':' +
(now.getMinutes() < 10 ? '0' : '') +
now.getMinutes()
)
}
}
}
}
return (
now.getDate() +
'.' +
now.getMonth() +
'.' +
now.getFullYear() +
' ' +
(now.getHours() < 10 ? '0' : '') +
now.getHours() +
':' +
(now.getMinutes() < 10 ? '0' : '') +
now.getMinutes()
)
}
}
},
beforeDestroy() {
clearInterval(this.timer)
}
}
</script>
<style scoped></style>

View File

@ -1,458 +0,0 @@
<template>
<div>
<v-card v-if="user" :loading="loading" style="margin-top: 3px">
<v-card-title>{{ user.firstname }} {{ user.lastname }}</v-card-title>
<v-card-text>
<v-row>
<v-col cols="12" sm="6">
<v-text-field
outlined
label="Vornamen"
:placeholder="user.firstname"
v-model="firstname"
readonly
/>
</v-col>
<v-col cols="12" sm="6">
<v-text-field
outlined
label="Nachname"
:placeholder="user.lastname"
v-model="lastname"
readonly
/>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="6">
<v-text-field
outlined
label="Benutzername"
:placeholder="user.username"
v-model="username"
readonly
></v-text-field>
</v-col>
<v-col cols="12" sm="6">
<v-text-field
ref="mail"
outlined
label="E-Mail"
:placeholder="user.mail"
v-model="mail"
readonly
></v-text-field>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="6">
<v-text-field
outlined
label="neues Password"
type="password"
v-model="password"
/>
</v-col>
<v-col cols="12" sm="6">
<v-form ref="newPassword">
<v-text-field
ref="password"
v-model="controlPassword"
outlined
label="neues Password bestätigen"
type="password"
:disabled="!password"
:rules="[equal_password]"
/>
</v-form>
</v-col>
</v-row>
<v-divider />
<v-row>
<v-col cols="12" sm="4">
<v-text-field
outlined
label="Sperrlimit"
readonly
:value="(user.limit / 100).toFixed(2).toString() + '€'"
/>
</v-col>
<v-col cols="12" sm="4">
<v-combobox
outlined
label="Sperrstatus"
v-model="lock"
append-icon
readonly
>
<template v-slot:selection="data">
<v-chip :color="lockColor">
{{ data.item }}
</v-chip>
</template>
</v-combobox>
</v-col>
<v-col cols="12" sm="4">
<v-combobox
outlined
label="Autosperre"
v-model="autoLock"
readonly
append-icon
>
<template v-slot:selection="data">
<v-chip :color="autoLockColor">
{{ data.item }}
</v-chip>
</template>
</v-combobox>
</v-col>
</v-row>
<v-row>
<v-col v-bind:class="{ fulllineText: isFulllineText }">
<v-combobox
outlined
multiple
label="Gruppen"
readonly
v-model="user.group"
append-icon
>
<template v-slot:selection="data">
<v-icon class="ma-2">{{
data.item === 'user'
? person
: data.item === 'bar'
? bar
: data.item === 'moneymaster'
? finanzer
: data.item === 'gastro'
? gastro
: ''
}}</v-icon>
</template>
</v-combobox>
</v-col>
<v-col v-bind:class="{ fulllineText: isFulllineText }">
<v-text-field
outlined
:value="computeStatus"
readonly
label="Mitgliedsstatus"
/>
</v-col>
<v-col v-bind:class="{ fulllineText: isFulllineText }">
<v-text-field
outlined
:value="user.voting ? 'ja' : 'nein'"
readonly
label="Stimmrecht"
/>
</v-col>
</v-row>
<v-row>
<v-col v-bind:class="{ fulllineText: isFulllineText }">
<v-combobox
chips
outlined
multiple
label="Arbeitsgruppen"
readonly
v-model="user.workgroups"
item-value="id"
item-text="name"
append-icon
>
<template v-slot:selection="data">
<v-chip>{{ data.item.name }}</v-chip>
</template>
</v-combobox>
</v-col>
</v-row>
<div class="subtitle-1">
Gespeicherte Sessions
</div>
<v-card v-for="token in tokens" :key="token.id" outlined>
<v-card-text>
<v-row>
<v-col>
<v-col>
Betriebssystem
</v-col>
<v-col>
<v-icon>
{{
token.platform === 'macos' || token.platform === 'iphone'
? apple
: token.platform === 'windows'
? windows
: token.platform === 'android'
? android
: token.platform === 'linux'
? linux
: token.platfrom
}}
</v-icon>
<v-icon
v-if="
token.platform === 'macos' || token.platform === 'iphone'
"
>
{{ token.platform === 'macos' ? mac : iphone }}
</v-icon>
</v-col>
</v-col>
<v-col>
<v-col>
Browser
</v-col>
<v-col>
<v-icon>
{{
token.browser === 'chrome'
? chrome
: token.browser === 'firefox'
? firefox
: token.browser === 'opera'
? opera
: token.browser === 'safari'
? safari
: token.browser === 'msie'
? msie
: token.browser
}}
</v-icon>
</v-col>
</v-col>
<v-col>
<v-col>
Letzte Aktualisierung
</v-col>
<v-col>
{{ token.timestamp.day }}.{{ token.timestamp.month }}.{{
token.timestamp.year
}}
um
{{
10 > token.timestamp.hour
? '0' + String(token.timestamp.hour)
: token.timestamp.hour
}}:{{
10 > token.timestamp.minute
? '0' + String(token.timestamp.minute)
: token.timestamp.minute
}}:{{
10 > token.timestamp.second
? '0' + String(token.timestamp.second)
: token.timestamp.second
}}</v-col
>
</v-col>
<v-col>
<v-col>
Lebenszeit
</v-col>
<v-col>
{{ calcLifefime(token.lifetime) }}
</v-col>
</v-col>
<v-col class="text-right">
<v-btn icon @click="deleteToken(token)">
<v-icon>
{{ trashCan }}
</v-icon>
</v-btn>
</v-col>
</v-row>
</v-card-text>
</v-card>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-form ref="acceptedPasswordTest">
<v-text-field
outlined
label="Passwort"
v-model="acceptedPassword"
type="password"
ref="acceptedPassword"
:rules="[empty_password]"
></v-text-field>
</v-form>
<v-btn text color="primary" @click="save">Speichern</v-btn>
</v-card-actions>
<v-snackbar
v-if="error ? error.value : false"
:color="error ? (error.error ? 'error' : 'success') : ''"
:value="error"
v-model="error"
:timeout="0"
>
{{ error ? error.value : null }}
</v-snackbar>
</v-card>
</div>
</template>
<script>
import {
mdiAccount,
mdiGlassCocktail,
mdiCurrencyEur,
mdiFoodForkDrink,
mdiApple,
mdiGoogleChrome,
mdiFirefox,
mdiOpera,
mdiInternetExplorer,
mdiAppleSafari,
mdiLaptopMac,
mdiCellphoneIphone,
mdiTrashCan,
mdiAndroid,
mdiWindows,
mdiLinux
} from '@mdi/js'
import { mapGetters, mapActions } from 'vuex'
export default {
name: 'Config',
data() {
return {
apple: mdiApple,
mac: mdiLaptopMac,
iphone: mdiCellphoneIphone,
android: mdiAndroid,
windows: mdiWindows,
linux: mdiLinux,
chrome: mdiGoogleChrome,
firefox: mdiFirefox,
opera: mdiOpera,
msie: mdiInternetExplorer,
safari: mdiAppleSafari,
person: mdiAccount,
bar: mdiGlassCocktail,
finanzer: mdiCurrencyEur,
gastro: mdiFoodForkDrink,
username: null,
mail: null,
firstname: null,
lastname: null,
password: null,
controlPassword: null,
trashCan: mdiTrashCan,
isFulllineText: false,
acceptedPassword: null,
passError: null,
equal_password: value =>
this.password === value || 'Passwörter sind nicht identisch.',
email: value => {
if (value.length > 0) {
const pattern = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
return pattern.test(value) || 'keine gültige E-Mail'
}
return true
},
empty_password: data => {
return !!data || 'Password wird bentögigt'
}
}
},
mounted() {
this.$nextTick(function() {
window.addEventListener('resize', this.getWindowWidth)
this.getWindowWidth()
})
},
methods: {
...mapActions({
saveConfig: 'user/saveConfig',
getStatus: 'user/getStatus',
getTokens: 'user/getTokens',
deleteToken: 'user/deleteToken'
}),
getWindowWidth() {
this.isFulllineText = document.documentElement.clientWidth <= 600
},
save() {
let user = {}
if (this.firstname) user.firstname = this.firstname
if (this.lastname) user.lastname = this.lastname
if (this.username) user.username = this.username
if (this.$refs.mail.validate()) {
if (this.mail) user.mail = this.mail
}
if (this.$refs.newPassword.validate()) {
if (this.password) user.password = this.password
} else {
return
}
console.log(this.$refs.acceptedPasswordTest.validate())
if (this.$refs.acceptedPasswordTest.validate()) {
this.saveConfig({
oldUsername: user.username,
...user,
acceptedPassword: this.acceptedPassword
})
this.$refs.acceptedPassword.reset()
} else {
this.passError = 'Du musst dein Password eingeben'
}
this.password = null
this.controlPassword = null
},
calcLifefime(time) {
if (time < 60) return String(time) + 'Sekunden'
time = Math.round(time / 60)
if (time < 60) return String(time) + 'Minuten'
time = Math.round(time / 60)
if (time < 24) return String(time) + 'Stunden'
time = Math.round(time / 24)
if (time < 7) return String(time) + 'Tage'
time = Math.round(time / 7)
if (time < 30) return String(time) + 'Wochen'
time = Math.round(time / 30)
if (time < 12) return String(time) + 'Monate'
time = Math.round(time / 12)
return String(time) + 'Jahre'
}
},
computed: {
...mapGetters({
user: 'user/user',
error: 'user/error',
loading: 'user/loading',
status: 'user/status',
tokens: 'user/tokens'
}),
lock() {
return this.user.locked ? 'gesperrt' : 'nicht gesperrt'
},
lockColor() {
return this.user.locked ? 'red' : 'green'
},
autoLock() {
return this.user.autoLock ? 'aktiviert' : 'deaktiviert'
},
autoLockColor() {
return this.user.autoLock ? 'green' : 'red'
},
computeStatus() {
try {
return this.status.find(a => a.id == this.user.statusgroup).name
} catch (e) {
return null
}
}
},
created() {
this.getStatus()
this.getTokens()
}
}
</script>
<style scoped>
.fulllineText {
flex-basis: unset;
}
</style>

View File

@ -1,116 +0,0 @@
<template>
<div>
<v-toolbar>
<v-toolbar-title>Gesamtübersicht</v-toolbar-title>
<v-spacer />
<v-toolbar-items>
<v-text-field
v-model="filter"
style="margin-top: 3px"
outlined
type="number"
:rules="[isNumber]"
>
<template v-slot:append>
<v-icon>{{magnify}}</v-icon>
</template>
</v-text-field>
</v-toolbar-items>
</v-toolbar>
<CreditOverviewSkeleton v-if="loading" />
<div v-for="year in years" :key="years.indexOf(year)">
<v-card style="margin-top: 3px" v-if="isFiltered(year)">
<v-card-title>{{ year }}</v-card-title>
<Table v-bind:user="user" v-bind:year="year" />
<v-container fluid>
<v-col>
<v-row>
<v-col>
<v-label>Vorjahr:</v-label>
</v-col>
<v-col>
<v-chip
outlined
:text-color="getLastColor(user.creditList[year][1].last)"
>{{ (user.creditList[year][1].last / 100).toFixed(2) }}
</v-chip>
</v-col>
<v-col>
<v-label>Gesamt:</v-label>
</v-col>
<v-col>
<v-chip
outlined
x-large
:text-color="
getLastColor(
getAllSum(
user.creditList[year][2].sum,
user.creditList[year][1].last
)
)
"
>
{{
(
getAllSum(
user.creditList[year][2].sum,
user.creditList[year][1].last
) / 100
).toFixed(2)
}}
</v-chip>
</v-col>
</v-row>
</v-col>
</v-container>
</v-card>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import Table from '../finanzer/Table'
import CreditOverviewSkeleton from './Skeleton/CreditOverviewSkeleton'
import { mdiMagnify } from '@mdi/js'
export default {
name: 'CreditOverview',
components: { CreditOverviewSkeleton, Table },
data() {
return {
isNumber: value => Number.isInteger(parseInt(value === '' ? 0 : value)) || "Muss eine Zahl sein.",
filter: '',
magnify: mdiMagnify
}
},
methods: {
getLastColor(value) {
return value < 0 ? 'red' : 'green'
},
getAllSum(sum, lastYear) {
return lastYear + sum
},
isFiltered(value) {
return value.toString().includes(this.filter)
}
},
computed: {
...mapGetters({
user: 'user/user',
loading: 'user/loading'
}),
years() {
let years = []
if (this.user) {
for (let year in this.user.creditList) {
years.unshift(parseInt(year))
}
}
return years
}
}
}
</script>
<style scoped></style>

View File

@ -1,208 +0,0 @@
<template>
<div>
<v-card tile :loading="jobInvitesLoading">
<v-card-title>
Eingehende Einladungen
</v-card-title>
<v-card-text>
<v-expansion-panels>
<v-expansion-panel
v-for="jobInvite in jobInvitesToMe"
:key="jobInvite.id"
@click.once="seenJobIvnite(jobInvite)"
>
<v-expansion-panel-header>
<div>
{{ jobInvite.on_date.getDate() }}.{{
jobInvite.on_date.getMonth() + 1
}}.{{ jobInvite.on_date.getFullYear() }} von
<v-badge dot :value="!jobInvite.watched" color="red">
{{ jobInvite.from_user.firstname }}
{{ jobInvite.from_user.lastname }}
</v-badge>
</div>
<v-row class="text-right" style="margin-right: 5px">
<v-col>
<v-progress-circular
indeterminate
v-if="jobInvitesLoading"
></v-progress-circular>
</v-col>
<v-col>
<v-icon color="green" v-show="userInWorker(jobInvite)">
{{ check }}
</v-icon>
</v-col>
</v-row>
</v-expansion-panel-header>
<v-expansion-panel-content :eager="true">
<v-row class="text-right">
<v-col>
<v-btn icon @click="updatingJobInvite(jobInvite)">
<v-icon>
{{ jobInvite.watched ? seen : notSeen }}
</v-icon>
</v-btn>
</v-col>
</v-row>
<Day
:day="jobInvite.day"
:long="true"
:loading="jobInvite.day.loading"
@addingJob="addingJob(jobInvite, $event)"
@deletingJob="deletingJob(jobInvite, $event)"
@sendInvites="setJobInvites"
@sendRequests="setJobRequests"
@deleteJobInvite="deleteInvite"
@deleteJobRequest="deleteRequest"
/>
</v-expansion-panel-content>
</v-expansion-panel>
</v-expansion-panels>
</v-card-text>
</v-card>
<v-card tile :loading="jobInvitesLoading">
<v-card-title>
Versendete Einladungen
</v-card-title>
<v-card-text>
<v-expansion-panels>
<v-expansion-panel
v-for="jobInvite in jobInvitesFromMe"
:key="jobInvite.id"
@click.once="seenJobIvnite(jobInvite)"
>
<v-expansion-panel-header>
<div>
{{ jobInvite.on_date.getDate() }}.{{
jobInvite.on_date.getMonth() + 1
}}.{{ jobInvite.on_date.getFullYear() }} an
<v-badge :value="jobInvite.watched" icon="mdi-eye" color="grey" inline>
{{ jobInvite.to_user.firstname }}
{{ jobInvite.to_user.lastname }}
</v-badge>
</div>
<v-row class="text-right" style="margin-right: 5px">
<v-col>
<v-progress-circular
indeterminate
v-if="jobInvitesLoading"
></v-progress-circular>
</v-col>
<v-col>
<v-icon color="green" v-show="userInWorker(jobInvite)">
{{ check }}
</v-icon>
</v-col>
</v-row>
</v-expansion-panel-header>
<v-expansion-panel-content :eager="true">
<v-row class="text-right">
<v-col>
<v-btn icon @click="deleteInvite(jobInvite)">
<v-icon>
{{trashCan}}
</v-icon>
</v-btn>
</v-col>
</v-row>
<Day
:day="jobInvite.day"
:long="true"
:loading="jobInvite.day.loading"
@addingJob="addingJob(jobInvite, $event)"
@deletingJob="deletingJob(jobInvite, $event)"
@sendInvites="setJobInvites"
@sendRequests="setJobRequests"
@deleteJobInvite="deleteInvite"
@deleteJobRequest="deleteRequest"
/>
</v-expansion-panel-content>
</v-expansion-panel>
</v-expansion-panels>
</v-card-text>
</v-card>
</div>
</template>
<script>
import { mapActions, mapGetters } from 'vuex'
import { mdiEyeOff, mdiEyeCheck, mdiCheck, mdiTrashCan } from '@mdi/js'
import Day from '@/components/user/Jobs/Day'
export default {
name: 'JobInvites',
components: { Day },
data() {
return {
notSeen: mdiEyeOff,
seen: mdiEyeCheck,
check: mdiCheck,
trashCan: mdiTrashCan,
showNotSeen: false,
showSeen: false,
update: 0
}
},
methods: {
...mapActions({
getJobInvites: 'jobInvites/getJobInvites',
addJob: 'jobInvites/addJob',
setJobInvites: 'jobInvites/setJobInvites',
updateJobInviteToMe: 'jobInvites/updateJobInviteToMe',
deleteJob: 'jobInvites/deleteJob',
setJobRequests: 'jobRequests/setJobRequests',
deleteInvite: 'jobInvites/deleteJobInviteFromMe',
deleteRequest: 'jobRequests/deleteJobRequestFromMe'
}),
forceRender() {
setTimeout(() => {
this.update += 0
}, 500)
},
updatingJobInvite(jobInvite) {
jobInvite.watched = !jobInvite.watched
this.updateJobInviteToMe(jobInvite)
},
seenJobIvnite(jobInvite) {
if (!jobInvite.watched) {
jobInvite.watched = true
this.updateJobInviteToMe(jobInvite)
}
},
addingJob(jobInvite, event) {
this.seenJobIvnite(jobInvite)
this.addJob(event)
this.forceRender()
},
deletingJob(jobInvite, event) {
this.seenJobIvnite(jobInvite)
this.deleteJob(event)
this.forceRender()
},
userInWorker(jobinvite) {
var jobkinddate = jobinvite.day.jobkinddate.find(item => {
return item.worker.find(workeritem => {
return workeritem.id === jobinvite.to_user.id
})
})
return !!jobkinddate
}
},
computed: {
...mapGetters({
jobInvitesFromMe: 'jobInvites/jobInvitesFromMe',
jobInvitesToMe: 'jobInvites/jobInvitesToMe',
jobInvitesLoading: 'jobInvites/jobInvitesLoading',
activeUser: 'user/user'
})
},
created() {
setTimeout(() => {
this.getJobInvites(new Date())
}, 200)
}
}
</script>
<style scoped></style>

View File

@ -1,224 +0,0 @@
<template>
<div>
<v-card tile :loading="jobRequestsLoading">
<v-card-title>
Eingehende Anfragen
</v-card-title>
<v-card-text>
<v-expansion-panels>
<v-expansion-panel
v-for="(jobrequest, index) in jobRequestsToMe"
:key="index"
>
<v-expansion-panel-header @click.once="seenJobRequest(jobrequest)">
<div>
{{ jobrequest.on_date.getDate() }}.{{
jobrequest.on_date.getMonth() + 1
}}.{{ jobrequest.on_date.getFullYear() }} von
<v-badge dot :value="!jobrequest.watched" color="red">
{{ jobrequest.from_user.firstname }}
{{ jobrequest.from_user.lastname }}
</v-badge>
</div>
<v-row class="text-right" style="margin-right: 5px">
<v-col>
<v-progress-circular
indeterminate
v-if="jobRequestsLoading"
></v-progress-circular>
</v-col>
<v-col>
<v-icon color="green" v-show="userInWorker(jobrequest)">
{{ check }}
</v-icon>
</v-col>
</v-row>
</v-expansion-panel-header>
<v-expansion-panel-content>
<v-row class="text-right">
<v-col>
<v-btn icon @click="updatingSeenJobRequest(jobrequest)">
<v-icon>
{{ jobrequest.watched ? seen : notSeen }}
</v-icon>
</v-btn>
</v-col>
</v-row>
<Day
:day="jobrequest.day"
:long="true"
@sendRequests="sendingJobRequests(jobrequest, $event)"
@addingJob="addJob"
@deletingJob="deleteJob"
@sendInvites="setJobInvites"
@deleteJobInvite="deleteInvite"
@deleteJobRequest="deleteJobRequestFromMe"
/>
<v-row class="text-right">
<v-col>
<v-btn
v-show="!jobrequest.answered"
text
@click="updatingAcceptedJobRequest(jobrequest)"
>Annehmen</v-btn
>
<div v-show="jobrequest.answered && !jobrequest.accepted">
Dieser Dienst wurde schon übertragen.
</div>
</v-col>
</v-row>
</v-expansion-panel-content>
</v-expansion-panel>
</v-expansion-panels>
</v-card-text>
</v-card>
<v-card tile :loading="jobRequestsLoading">
<v-card-title>
Ausgehende Anfragen
</v-card-title>
<v-card-text>
<v-expansion-panels>
<v-expansion-panel
v-for="(jobrequest, index) in jobRequestsFromMe"
:key="index"
>
<v-expansion-panel-header>
<div>
{{ jobrequest.on_date.getDate() }}.{{
jobrequest.on_date.getMonth() + 1
}}.{{ jobrequest.on_date.getFullYear() }} an
<v-badge :value="jobrequest.watched" icon="mdi-eye" color="grey" inline>
{{ jobrequest.to_user.firstname }}
{{ jobrequest.to_user.lastname }}
</v-badge>
</div>
<v-row class="text-right" style="margin-right: 5px">
<v-col>
<v-progress-circular
indeterminate
v-if="jobRequestsLoading"
></v-progress-circular>
</v-col>
<v-col>
<v-icon color="green" v-show="jobrequest.accepted">
{{ check }}
</v-icon>
</v-col>
</v-row>
</v-expansion-panel-header>
<v-expansion-panel-content>
<v-row class="text-right">
<v-col>
<v-btn icon @click="deleteJobRequestFromMe(jobrequest)">
<v-icon>
{{trashCan}}
</v-icon>
</v-btn>
</v-col>
</v-row>
<Day
:day="jobrequest.day"
:long="true"
@sendRequests="sendingJobRequests(jobrequest, $event)"
@addingJob="addJob"
@deletingJob="deleteJob"
@sendInvites="setJobInvites"
@deleteJobInvite="deleteInvite"
@deleteJobRequest="deleteJobRequestFromMe"
/>
</v-expansion-panel-content>
</v-expansion-panel>
</v-expansion-panels>
</v-card-text>
</v-card>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex'
import { mdiEyeOff, mdiEyeCheck, mdiCheck, mdiTrashCan } from '@mdi/js'
import Day from '@/components/user/Jobs/Day'
export default {
name: 'JobTransfer',
components: { Day },
props: {},
data() {
return {
notSeen: mdiEyeOff,
seen: mdiEyeCheck,
check: mdiCheck,
trashCan: mdiTrashCan,
}
},
methods: {
...mapActions({
getJobRequests: 'jobRequests/getJobRequests',
updateJobRequestToMe: 'jobRequests/updateJobRequestToMe',
setJobRequests: 'jobRequests/setJobRequests',
deleteJobRequestFromMe: 'jobRequests/deleteJobRequestFromMe',
deleteInvite: 'jobInvites/deleteJobInviteFromMe',
getJobInvites: 'jobInvites/getJobInvites',
addJob: 'jobInvites/addJob',
setJobInvites: 'jobInvites/setJobInvites',
updateJobInviteToMe: 'jobInvites/updateJobInviteToMe',
deleteJob: 'jobInvites/deleteJob',
}),
updatingAcceptedJobRequest(jobRequest) {
jobRequest.accepted = true
jobRequest.answered = true
this.updateJobRequestToMe({ ...jobRequest })
setTimeout(() => {
this.getJobRequests(), 200
})
},
updatingSeenJobRequest(jobRequest) {
jobRequest.watched = !jobRequest.watched
this.updateJobRequestToMe({ ...jobRequest })
},
seenJobRequest(jobRequest) {
if (!jobRequest.watched) {
jobRequest.watched = true
this.updateJobRequestToMe(jobRequest)
}
},
userInWorker(jobrequest) {
var jobkinddate = jobrequest.day.jobkinddate.find(item => {
return item.worker.find(workeritem => {
return workeritem.id === this.activeUser.id
})
})
return !!jobkinddate
},
sendingJobRequests(jobrequest, event) {
this.seenJobRequest(jobrequest)
this.setJobRequests(event)
}
},
computed: {
...mapGetters({
jobRequestsToMe: 'jobRequests/jobRequestsToMe',
jobRequestsFromMe: 'jobRequests/jobRequestsFromMe',
jobRequestsLoading: 'jobRequests/jobRequestsLoading',
loading: 'user/loading',
activeUser: 'user/user'
})
},
created() {
if (!this.loading) {
this.getJobRequests()
}
},
watch: {
loading(newValue) {
if (!newValue) {
this.getJobRequests()
}
},
jobRequestsLoading(newValue, oldValue) {
console.log(newValue, oldValue)
}
}
}
</script>
<style scoped></style>

View File

@ -1,192 +0,0 @@
<template>
<div>
<v-toolbar>
<v-toolbar-title>Dienstübersicht</v-toolbar-title>
<v-spacer />
<v-toolbar-items>
<v-btn
text
icon
:to="{
name: 'userJobs',
params: { year: date.getFullYear(), month: date.getMonth() }
}"
>
<v-icon>{{ keyboard_arrow_left }}</v-icon>
</v-btn>
<v-list-item>
<v-list-item-title class="title">
{{ monthArray[date.getMonth()] }}
{{ date.getFullYear() }}
</v-list-item-title>
</v-list-item>
<v-btn
text
icon
:to="{
name: 'userJobs',
params: { year: date.getFullYear(), month: date.getMonth() + 2 }
}"
>
<v-icon>{{ keyboard_arrow_right }}</v-icon>
</v-btn>
</v-toolbar-items>
<v-spacer />
</v-toolbar>
<v-card v-for="week in month" :key="month.indexOf(week)" flat tile>
<v-card-title class="subtitle-1 font-weight-bold">
Woche vom {{ week.startDate.getDate() }}.{{
week.startDate.getMonth() + 1
}}.{{ week.startDate.getFullYear() }} bis
{{ week.endDate.getDate() }}.{{ week.endDate.getMonth() + 1 }}.{{
week.endDate.getFullYear()
}}
</v-card-title>
<v-card-text>
<v-row justify="start" align="start">
<div v-for="day in week.days" :key="day.id">
<v-col cols="12">
<Day
:day="day"
:long="false"
@addingJob="addJob"
@sendInvites="setJobInvites"
@deletingJob="deleteJob"
@sendRequests="setJobRequests"
@deleteJobInvite="deleteInvite"
@deleteJobRequest="deleteRequest"
/>
</v-col>
</div>
</v-row>
</v-card-text>
</v-card>
</div>
</template>
<script>
import { mdiChevronLeft, mdiChevronRight } from '@mdi/js'
import { mapGetters, mapActions } from 'vuex'
import Day from '@/components/user/Jobs/Day'
export default {
name: 'Jobs',
components: { Day },
data() {
return {
keyboard_arrow_left: mdiChevronLeft,
keyboard_arrow_right: mdiChevronRight,
date: new Date(this.$route.params.year, this.$route.params.month - 1, 1),
monthArray: [
'Januar',
'Februar',
'März',
'April',
'Mai',
'Juni',
'Juli',
'August',
'September',
'Oktober',
'November',
'Dezember'
]
}
},
created() {
this.getActiveUser()
this.getAllJobKinds()
this.createMonth(this.date)
this.getDBUsers()
this.getUsers({
from_date: {
year: this.startDate.getFullYear(),
month: this.startDate.getMonth() + 1,
day: this.startDate.getDate()
},
to_date: {
year: this.endDate.getFullYear(),
month: this.endDate.getMonth() + 1,
day: this.endDate.getDate()
}
})
},
methods: {
...mapActions({
getActiveUser: 'user/getUser',
createMonth: 'jobs/createMonth',
getUsers: 'jobs/getUsers',
getDBUsers: 'usermanager/getUsers',
getAllJobKinds: 'jkm/getAllJobKinds',
addJob: 'jobs/addJob',
deleteJob: 'jobs/deleteJob',
setJobInvites: 'jobInvites/setJobInvites',
getJobInvites: 'jobInvites/getJobInvites',
getJobRequests: 'jobRequests/getJobRequests',
setJobRequests: 'jobRequests/setJobRequests',
deleteInvite: 'jobInvites/deleteJobInviteFromMe',
deleteRequest: 'jobRequests/deleteJobRequestFromMe'
}),
changeMonth(value) {
if (value === -1) {
this.date = new Date(this.date.getFullYear(), this.date.getMonth() - 1)
} else {
this.date = new Date(this.date.getFullYear(), this.date.getMonth() + 1)
}
this.createMonth(this.date)
this.getUsers({
from_date: {
year: this.startDate.getFullYear(),
month: this.startDate.getMonth() + 1,
day: this.startDate.getDate()
},
to_date: {
year: this.endDate.getFullYear(),
month: this.endDate.getMonth() + 1,
day: this.endDate.getDate()
}
})
}
},
computed: {
...mapGetters({
month: 'jobs/month',
startDate: 'jobs/getStartDate',
endDate: 'jobs/getEndDate',
loading: 'user/loading'
})
},
watch: {
$route() {
this.getActiveUser()
this.date = new Date(
this.$route.params.year,
this.$route.params.month - 1,
1
)
this.getAllJobKinds()
this.createMonth(this.date)
this.getDBUsers()
this.getUsers({
from_date: {
year: this.startDate.getFullYear(),
month: this.startDate.getMonth() + 1,
day: this.startDate.getDate()
},
to_date: {
year: this.endDate.getFullYear(),
month: this.endDate.getMonth() + 1,
day: this.endDate.getDate()
}
})
},
loading(newValue) {
if (!newValue) {
this.getJobInvites()
this.getJobRequests()
}
}
}
}
</script>
<style scoped></style>

View File

@ -1,438 +0,0 @@
<template>
<div v-if="day">
<v-card :color="color(day)" :max-width="long ? '' : '20em'">
<v-card-title v-if="day.date" class="subtitle-1 font-weight-bold">
{{ name }} den {{ day.date.getDate() }}.{{ day.date.getMonth() + 1 }}.{{
day.date.getFullYear()
}}
</v-card-title>
<v-card-text>
<v-expand-transition>
<v-row align="center" justify="center" v-if="day.loading">
<v-progress-circular indeterminate color="grey" />
</v-row>
</v-expand-transition>
<v-expand-transition>
<div v-show="!day.loading">
<div
v-for="(jobkinddateitem, index) in day.jobkinddate"
:key="index"
>
<div class="title black--text text-sm-center">
{{ jobkinddateitem.job_kind.name }}
</div>
<v-combobox
multiple
filled
disabled
readonly
:counter="jobkinddateitem.maxpersons"
v-model="jobkinddateitem.worker"
:item-text="item => item.firstname + ' ' + item.lastname"
item-value="username"
chips
append-icon=""
>
<template v-slot:selection="data">
<v-chip
>{{ data.item.firstname }} {{ data.item.lastname }}</v-chip
>
</template>
</v-combobox>
</div>
</div>
</v-expand-transition>
</v-card-text>
<v-card-actions class="text--secondary" v-if="!day.loading" :key="update">
<v-spacer />
<v-menu
v-model="menu"
open-on-hover
close-on-click
close-on-content-click
offset-y
>
<template v-slot:activator="{ on }">
<v-btn
text
v-on="on"
v-show="filterAddJob.length > 0 && !userInWorker() && !day.locked"
>
Eintragen
</v-btn>
</template>
<v-list>
<v-list-item
v-for="(jobkinddateitem, index) in filterAddJob"
:key="index"
@click="addingJob(jobkinddateitem)"
>
<v-list-item-title>
{{ jobkinddateitem.job_kind.name }}
</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
<v-menu v-model="menu_invite" :close-on-content-click="false" offset-y>
<template v-slot:activator="{ on }">
<v-btn
v-show="
jobkindWithSpace.length !== 0 && !day.locked && userInWorker()
"
text
v-on="on"
>Einladen</v-btn
>
</template>
<v-card :loading="jobInvitesLoading">
<v-card-title>
Einladungen
</v-card-title>
<v-card-text>
<v-div
bottom
v-for="(invite, index) in getInvites(day.date)"
:key="index"
>
<v-chip style="margin: 5px">
{{ invite.to_user.firstname }}
{{ invite.to_user.lastname }}
</v-chip>
</v-div>
<v-divider style="margin-bottom: 5px" />
<v-autocomplete
v-model="job_invites"
label="Einladungen senden an"
return-object
multiple
filled
:item-text="item => item.firstname + ' ' + item.lastname"
item-value="id"
chips
deletable-chips
:items="filteredDBUsers()"
>
</v-autocomplete>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn
text
:disabled="job_invites.length === 0"
@click="sendInvites()"
>
{{ job_invites.length >= 1 ? 'Einladungen' : 'Einladung' }}
senden
</v-btn>
</v-card-actions>
</v-card>
</v-menu>
<v-menu
v-model="menu_request"
:close-on-content-click="false"
offset-y
v-if="day.locked && userInWorker()"
>
<template v-slot:activator="{ on }">
<v-btn v-show="day.locked && userInWorker()" text v-on="on">
Abgeben
</v-btn>
</template>
<v-card :loading="jobRequestsLoading">
<v-card-title>
Dienstabgabenanfrage
</v-card-title>
<v-card-text>
<div v-if="jobRequestsLoading">
<v-progress-circular indeterminate />
</div>
<div v-if="!jobRequestsLoading">
<v-div
bottom
v-for="(request, index) in getRequests(day.date)"
:key="index"
>
<v-chip style="margin: 5px">
{{ request.to_user.firstname }}
{{ request.to_user.lastname }}
</v-chip>
</v-div>
</div>
<v-divider style="margin-bottom: 5px" />
<v-autocomplete
v-model="job_requests"
label="Anfragen senden an"
return-object
multiple
filled
:item-text="item => item.firstname + ' ' + item.lastname"
item-value="id"
chips
deletable-chips
:items="filteredDBUsersWithJobKind()"
>
</v-autocomplete>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn text @click="sendRequests()">
Anfragen
</v-btn>
</v-card-actions>
</v-card>
</v-menu>
<v-btn
v-show="!day.locked && userInWorker()"
text
@click="deletingJob()"
>Austragen</v-btn
>
</v-card-actions>
</v-card>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex'
import { mdiAccountPlus } from '@mdi/js'
export default {
name: 'Day',
props: {
day: Object,
long: Boolean,
loading: Boolean
},
data() {
return {
account_add: mdiAccountPlus,
searchInput: null,
requestUser: null,
menu: false,
menu_invite: false,
menu_request: false,
update: 0,
job_invites: [],
job_requests: []
}
},
created() {
//setInterval(() => {console.log(this.day.loading)},100)
},
methods: {
...mapActions({}),
sendInvites() {
var sendData = []
this.job_invites.forEach(item => {
sendData.push({
from_user: this.activeUser,
to_user: item,
date: {
year: this.day.date.getFullYear(),
month: this.day.date.getMonth() + 1,
day: this.day.date.getDate()
}
})
})
this.$emit('sendInvites', sendData)
setTimeout(() => {
this.job_invites = []
}, 200)
this.menu_invite = false
},
sendRequests() {
var sendData = []
this.job_requests.forEach(item => {
sendData.push({
from_user: this.activeUser,
to_user: item,
date: {
year: this.day.date.getFullYear(),
month: this.day.date.getMonth() + 1,
day: this.day.date.getDate()
},
job_kind: this.getJob_Kind()
})
})
this.$emit('sendRequests', sendData)
setTimeout(() => {
this.job_requests = []
}, 200)
this.menu_request = false
},
color: day => {
if (day) {
if (day.date.getDay() === 0 || day.date.getDay() === 1) {
return 'grey lighten-4'
} else {
var retVal = 'yellow'
retVal = 'light-green'
for (var jobkind in day.jobkinddate) {
if (
day.jobkinddate[jobkind].worker.length >=
day.jobkinddate[jobkind].maxpersons
)
retVal = 'light-green'
else return 'yellow'
}
return retVal
}
} else {
return 'grey lighten-4'
}
},
user(worker) {
return worker.username === this.activeUser.username
},
addingJob(jobkinddateitem) {
this.$emit('addingJob', {
year: this.day.date.getFullYear(),
month: this.day.date.getMonth() + 1,
day: this.day.date.getDate(),
job_kind: jobkinddateitem.job_kind
})
this.menu = false
setTimeout(() => {
this.update += 1
}, 200)
},
deletingJob() {
this.$emit('deletingJob', {
year: this.day.date.getFullYear(),
month: this.day.date.getMonth() + 1,
day: this.day.date.getDate()
})
setTimeout(() => {
this.update += 1
})
},
userInWorker() {
var jobkinddate = this.day.jobkinddate.find(item => {
return item.worker.find(workeritem => {
return workeritem.id === this.activeUser.id
})
})
return !!jobkinddate
},
deletingInvite(jobInvite) {
let sendData = { ...jobInvite }
sendData.on_date = {
year: sendData.on_date.getFullYear(),
month: sendData.on_date.getMonth() + 1,
day: sendData.on_date.getDate()
}
this.$emit('deleteJobInvite', sendData)
},
deletingRequest(jobRequest) {
let sendData = { ...jobRequest }
sendData.on_date = {
year: sendData.on_date.getFullYear(),
month: sendData.on_date.getMonth() + 1,
day: sendData.on_date.getDate()
}
this.$emit('deleteJobRequest', sendData)
this.menu_request = false
setTimeout(() => {
this.menu_request = true
}, 200)
},
getJob_Kind() {
var jobkinddate = this.day.jobkinddate.find(item => {
return item.worker.find(user => {
return this.activeUser.id === user.id
})
})
return jobkinddate.job_kind
},
filteredDBUsersWithJobKind() {
var retVal = this.filteredDBUsers()
var job_kind = this.getJob_Kind()
retVal = retVal.filter(user => {
if (job_kind.workgroup ? job_kind.workgroup.if === null : true) {
return true
} else {
if (user.workgroups ? user.workgroups.length > 0 : false) {
return user.workgroups.find(wg => {
return wg.id === job_kind.workgroup.id
})
}
}
return false
})
return retVal
},
filteredDBUsers() {
var retVal = this.allUsers.filter(user => {
var test = this.day.jobkinddate.find(item => {
return item.worker.find(workeritem => {
return workeritem.id === user.id
})
})
return !test
})
retVal = retVal.filter(user => {
let getedDay = this.getDay(this.day.date)
let test = getedDay
? getedDay.find(day => {
return day.to_user.id === user.id
})
: false
return !test
})
return retVal
}
},
computed: {
...mapGetters({
activeUser: 'user/user',
allUsers: 'usermanager/users',
getDay: 'jobInvites/getDayFromMe',
getInvites: 'jobInvites/getDayWorkerFromMe',
getRequests: 'jobRequests/getDayWorkerFromMe',
jobInvitesLoading: 'jobInvites/jobInvitesLoading',
jobRequestsLoading: 'jobRequests/jobRequestsLoading'
}),
jobkindWithSpace() {
var retVal = this.day.jobkinddate.filter(item => {
if (item.maxpersons <= item.worker.length) return false
else return true
})
return retVal
},
filterAddJob() {
var retVal = this.day.jobkinddate.filter(item => {
if (item.maxpersons <= item.worker.length) {
return false
}
if (!item.job_kind.workgroup) {
return true
} else {
if (this.activeUser.workgroups) {
return this.activeUser.workgroups.find(workgroup => {
return workgroup.id === item.job_kind.workgroup.id
})
}
}
})
return retVal
},
name() {
const name = this.day.date.getDay()
if (name === 0) return 'Sonntag'
else if (name === 1) return 'Montag'
else if (name === 2) return 'Dienstag'
else if (name === 3) return 'Mittwoch'
else if (name == 4) return 'Donnerstag'
else if (name === 5) return 'Freitag'
else return 'Samstag'
},
dayLoading() {
return this.loading
}
}
}
</script>
<style scoped></style>

View File

@ -1,55 +0,0 @@
<template>
<div>
<v-bottom-navigation v-model="bottom_nav">
<v-btn :to="{ name: 'jobRequests', params: { kind: 'jobInvites' } }">
<v-badge color="red" :content="newsInvite" :value="newsInvite !== 0">
Diensteinladungen
</v-badge>
</v-btn>
<v-btn :to="{ name: 'jobRequests', params: { kind: 'jobTransfer' } }">
<v-badge color="red" :content="newsRequest" :value="newsRequest !== 0">Dienstübertragung</v-badge>
</v-btn>
</v-bottom-navigation>
<JobInvites v-if="kind === 'jobInvites'"></JobInvites>
<JobTransfer v-if="kind === 'jobTransfer'"></JobTransfer>
</div>
</template>
<script>
import JobInvites from '@/components/user/JobRequests/JobInvites'
import JobTransfer from '@/components/user/JobRequests/JobTransfer'
import { mapActions, mapGetters } from 'vuex'
export default {
name: 'JobsRequest',
components: { JobTransfer, JobInvites },
data() {
return {
bottom_nav: true,
kind: this.$route.params.kind
}
},
methods: {
...mapActions({
getUser: 'user/getUser',
getDBUsers: 'usermanager/getUsers',
})
},
computed: {
...mapGetters({
newsInvite: 'jobInvites/news',
newsRequest: 'jobRequests/news'
})
},
created() {
this.getUser()
this.getDBUsers()
},
watch: {
$route() {
this.kind = this.$route.params.kind
}
}
}
</script>
<style scoped></style>

View File

@ -1,50 +0,0 @@
<template>
<v-card>
<v-card-title>
<v-skeleton-loader type="heading" />
</v-card-title>
<v-card-text>
<v-row>
<v-col cols="10">
<v-row>
<v-col>
<v-skeleton-loader type="button" />
</v-col>
<v-col>
<v-skeleton-loader type="button" />
</v-col>
<v-col>
<v-skeleton-loader type="button" />
</v-col>
</v-row>
<v-row>
<v-col>
<v-skeleton-loader type="button" />
</v-col>
<v-col>
<v-skeleton-loader type="button" />
</v-col>
<v-col>
<v-skeleton-loader type="button" />
</v-col>
</v-row>
</v-col>
<v-col align-self="center">
<v-row>
<v-list-item>
<v-skeleton-loader type="chip" />
</v-list-item>
</v-row>
</v-col>
</v-row>
</v-card-text>
</v-card>
</template>
<script>
export default {
name: 'AddAmountSkeleton'
}
</script>
<style scoped></style>

View File

@ -1,35 +0,0 @@
<template>
<v-card style="margin-top: 3px">
<v-card-title><v-skeleton-loader type="heading"/></v-card-title>
<v-container>
<v-skeleton-loader type="table-thead" />
<v-skeleton-loader type="table-row-divider@3" />
</v-container>
<v-container fluid>
<v-col>
<v-row>
<v-col>
<v-skeleton-loader type="chip" />
</v-col>
<v-col>
<v-skeleton-loader type="chip" />
</v-col>
<v-col>
<v-skeleton-loader type="chip" />
</v-col>
<v-col>
<v-skeleton-loader type="chip" />
</v-col>
</v-row>
</v-col>
</v-container>
</v-card>
</template>
<script>
export default {
name: 'CreditOverviewSkeleton'
}
</script>
<style scoped></style>

View File

@ -1,107 +0,0 @@
<template>
<v-list>
<v-list-item link to="/main/user/add">
<v-list-item-icon>
<v-icon>{{ account }}</v-icon>
</v-list-item-icon>
<v-list-item-title>Home</v-list-item-title>
</v-list-item>
<v-list-item link :to="{ name: 'freedrinkUser' }">
<v-list-item-icon>
<v-icon>{{ beer }}</v-icon>
</v-list-item-icon>
<v-list-item-title>Freigetränk buchen</v-list-item-title>
</v-list-item>
<v-list-item link :to="{ name: 'userOverview' }">
<v-list-item-icon>
<v-icon>{{ bank }}</v-icon>
</v-list-item-icon>
<v-list-item-title>Finanzübersicht</v-list-item-title>
</v-list-item>
<v-list-item
link
:to="{
name: 'userJobs',
params: {
year: new Date().getFullYear(),
month: new Date().getMonth() + 1
}
}"
>
<v-list-item-icon>
<v-icon>{{ briefcase }}</v-icon>
</v-list-item-icon>
<v-list-item-title>Dienstübersicht</v-list-item-title>
</v-list-item>
<v-list-item link :to="{ name: 'jobRequests', params: { kind: 'jobInvites' } }">
<v-list-item-icon>
<v-badge overlap color="red" :content="news" :value="news !== 0">
<v-icon>{{ switchAccount }}</v-icon>
</v-badge>
</v-list-item-icon>
<v-list-item-title>Dienstanfragen</v-list-item-title>
</v-list-item>
<v-list-item link :to="{ name: 'userConfig' }">
<v-list-item-icon>
<v-icon>{{ account_card_details }}</v-icon>
</v-list-item-icon>
<v-list-item-title>Einstellung</v-list-item-title>
</v-list-item>
</v-list>
</template>
<script>
import {
mdiAccountCardDetails,
mdiHome,
mdiBank,
mdiBriefcase,
mdiAccountSwitch,
mdiBeer
} from '@mdi/js'
import { mapGetters, mapActions } from 'vuex'
export default {
name: 'UserNavigation',
data() {
return {
account_card_details: mdiAccountCardDetails,
account: mdiHome,
bank: mdiBank,
briefcase: mdiBriefcase,
switchAccount: mdiAccountSwitch,
beer: mdiBeer
}
},
methods: {
...mapActions({
getJobInvites: 'jobInvites/getJobInvites',
getJobRequests: 'jobRequests/getJobRequests',
getUser: 'user/getUser'
})
},
computed: {
...mapGetters({
newsInvite: 'jobInvites/news',
newsRequest: 'jobRequests/news',
loading: 'user/loading'
}),
news() {
return this.newsInvite + this.newsRequest
}
},
created() {
this.getUser()
},
watch: {
loading(newValue) {
if (!newValue) {
this.getJobInvites()
this.getJobRequests()
}
}
}
}
</script>
<style scoped></style>

View File

@ -1,474 +0,0 @@
<template>
<div>
<v-container>
<v-card class="mx-auto" outlined v-if="!first_loading" :loading="loading">
<v-card-title>
<div class="title">Freigetränk für Versammlungen/Arbeit</div>
<v-btn class="menuBtn" @click.stop="isMenuShow = !isMenuShow" icon>
<v-icon>{{ menuIcon }}</v-icon>
</v-btn>
</v-card-title>
<v-card-text>
<v-row v-if="!first_loading">
<v-col cols="12">
<v-row>
<v-col
v-for="freeDrink in free_drink_list_config_workgroup"
:key="freeDrink.id"
cols="6"
xs="3"
sm="4"
class="drinkCol"
>
<v-btn
class="drinkBtn"
block
:color="color_fix"
@click="addAmount(freeDrink, freeDrinkTypeWorkgroup)"
>{{ freeDrink.label }}</v-btn
>
</v-col>
</v-row>
<v-row v-if="!first_loading" class="justify-end">
<v-col cols="3">
<v-list-item>
<v-list-item-content class="text-center">
<v-list-item-action-text class="title">
{{
free_drink_list_history_workgroup_without_canceled.length
}}
Getränke
</v-list-item-action-text>
</v-list-item-content>
</v-list-item>
</v-col>
</v-row>
</v-col>
</v-row>
</v-card-text>
</v-card>
</v-container>
<v-navigation-drawer v-model="isMenuShow" right app clipped>
<v-list-item-group :key="componentRenderer">
<v-list-item inactive>
<v-list-item-title class="headline">Verlauf</v-list-item-title>
</v-list-item>
<v-divider />
<div
v-for="freeDrinkHistory in free_drink_list_history_workgroup"
:key="freeDrinkHistory.id"
>
<v-list-item
three-line
inactive
@click="canceledAmount(freeDrinkHistory)"
>
<v-list-item-content>
<v-list-item-title>{{
now(freeDrinkHistory.timestamp)
}}</v-list-item-title>
<v-list-item-subtitle>
{{ freeDrinkHistory.free_drink_config.label }} wurde für
{{
(freeDrinkHistory.free_drink_config.price / 100).toFixed(2)
}}
hinzugefügt.
</v-list-item-subtitle>
<v-list-item-subtitle
class="red--text"
v-if="freeDrinkHistory.canceled"
>STORNIERT!!!</v-list-item-subtitle
>
<v-list-item-action-text
v-if="
isStronoEnabled(freeDrinkHistory.timestamp) &&
!freeDrinkHistory.canceled
"
>Klicken um zu Stornieren</v-list-item-action-text
>
</v-list-item-content>
</v-list-item>
</div>
</v-list-item-group>
</v-navigation-drawer>
<v-dialog v-model="showConfirmCanceledDialog" max-width="290">
<v-card>
<v-card-title>Willst du wirklich??</v-card-title>
<v-card-text v-if="canceledMessage">
{{ canceledMessage.free_drink_type.name }}: Willst du wirklich ein
{{ canceledMessage.free_drink_config.label }} im Wert von
{{ (canceledMessage.free_drink_config.price / 100).toFixed(2) }}
stornieren?
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn text @click="cancelCanceled">Abbrechen</v-btn>
<v-btn text @click="acceptCanceled">Stornieren</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<v-dialog v-model="showReasonDialog" max-width="290">
<v-card>
<v-card-title>Willst du wirklich??</v-card-title>
<v-card-text>
<v-combobox
v-model="selectedReason"
:items="free_drink_list_reasons"
item-value="id"
item-text="name"
label="Grund fürs Getränk"
></v-combobox>
<v-textarea
label="Beschreibung"
v-model="reasonDescription"
></v-textarea>
<v-text-field
type="number"
label="Anzahl der Getränke"
v-model="drinkCount"
></v-text-field>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn text @click="cancelFreedrink">Abbrechen</v-btn>
<v-btn text @click="confirmFreeDrink">Bestätigen</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<v-container v-if="first_loading">
<AddAmountSkeleton />
</v-container>
<v-snackbar
:color="
snackbar_messages.length > 0
? snackbar_messages[0].error
? 'error'
: 'success'
: 'success'
"
bottom
:timeout="0"
:multi-line="true"
:value="
snackbar_messages.length > 0 ? snackbar_messages[0].visible : null
"
vertical
>
<v-list-item
v-for="(message, index) in snackbar_messages"
:key="index"
:style="
message.error
? 'background-color: #FF5252;'
: 'background-color: #4CAF50;'
"
v-show="message.visible"
>
<v-list-item-content>
<v-list-item-title style="color: white">
{{ createMessage(message) }}
</v-list-item-title>
</v-list-item-content>
<v-list-item-action v-if="message.error">
<v-btn icon @click="message.visible = false">
<v-icon color="white">
mdi-close
</v-icon>
</v-btn>
</v-list-item-action>
</v-list-item>
</v-snackbar>
</div>
</template>
<script>
// eslint-disable-next-line no-unused-vars
import AddAmountSkeleton from '../Skeleton/AddAmountSkeleton'
import { mapGetters, mapActions } from 'vuex'
import { mdiPlus, mdiMenu } from '@mdi/js'
export default {
name: 'FreedrinkUserView',
components: { AddAmountSkeleton },
created() {
// this.timer = setInterval(() => (this.componentRenderer += 1), 1000)
this.get_free_drink_list_config()
this.get_free_drink_list_history()
this.get_free_drink_list_reasons()
},
data() {
return {
plus: mdiPlus,
menuIcon: mdiMenu,
locked: false,
showConfirmCanceledDialog: false,
showReasonDialog: false,
customValue: null,
color: 'green accent-4',
color_fix: 'green accent-4',
messages: [],
amount: 0,
free_drink: null,
free_drink_type_id: null,
selectedDrink: null,
drinkCount: 1,
timer: '',
componentRenderer: 0,
isMenuShow: false,
selectedReason: null,
reasonDescription: null,
freeDrinkTypeWorkgroup: 2,
snackbarTimeout: 3000,
canceledMessage: null
}
},
methods: {
...mapActions('freeDrinkList', [
'get_free_drink_list_config',
'get_free_drink_list_history',
'get_free_drink_list_reasons',
'set_free_drink_list_history',
'update_free_drink_list_history'
]),
getReason() {
const reasonArray = []
if (!this.user) {
return
}
if (this.user.group.includes('user')) {
reasonArray.push('AG-Besprechung')
reasonArray.push('AGDSN')
}
if (this.user.group.includes('bar')) {
reasonArray.push('Dienstantrittsschnaps')
}
if (this.user.group.includes('vorstand')) {
reasonArray.push('Vorstandssitzung')
reasonArray.push('Vermietungsgespräch')
}
if (this.user.group.includes('gastro')) {
reasonArray.push('Helmke-Fahrern')
reasonArray.push('Vertretergespräch')
}
return reasonArray
},
addAmount(free_drink, free_drink_type_id) {
if (free_drink) {
this.showReasonDialog = true
this.free_drink = free_drink
this.free_drink_type_id = free_drink_type_id
}
},
stornoAmount(message) {
if (!this.isStronoEnabled(message.date) || message.storno) {
return
}
this.showConfirmStornoDialog = true
this.stornoMessage = message
},
cancelStorno() {
this.showConfirmStornoDialog = null
this.stornoMessage = null
},
confirmFreeDrink() {
if (this.drinkCount >= 0) {
this.set_free_drink_list_history({
...this.free_drink,
free_drink_type_id: this.free_drink_type_id,
free_drink_list_reason_id: this.selectedReason.id,
description: this.reasonDescription,
count: this.drinkCount
})
}
this.cancelFreedrink()
},
cancelFreedrink() {
this.showReasonDialog = false
this.amount = null
this.selectedReason = null
this.reasonDescription = null
this.drinkCount = 1
this.free_drink_type_id = null
this.free_drink = null
},
generateMessage() {
this.messages.push({
date: new Date(),
storno: false,
amount: this.amount,
reasonDescription: this.reasonDescription,
selectedReason: this.selectedReason,
drinkCount: this.drinkCount
})
},
createMessage(message) {
var text = ''
if (message.error) {
text =
'ERROR: ' +
message.free_drink_type.name +
': Konnte ' +
message.label +
' für ' +
(message.price / 100).toFixed(2) +
'€ nicht hinzufügen.'
} else if (message.canceled) {
text = `${message.free_drink_type.name}: ${message.label} wurde für ${(message.price/100).toFixed(2)}€ storniert.`
} else {
text =
message.free_drink_type.name +
': ' +
message.label +
' wurde für ' +
(message.price / 100).toFixed(2) +
'€ hinzugefügt.'
}
return text
},
checkLocked() {
this.locked = this.limit - this.amount <= 0
},
getColor() {
return this.locked ? 'title red--text' : 'title'
},
acceptStorno() {
this.stornoMessage.storno = true
this.amount -= this.stornoMessage.amount
console.log(this.amount, this.stornoMessage)
this.cancelStorno()
},
acceptCanceled() {
this.canceledMessage.canceled = true
this.update_free_drink_list_history(this.canceledMessage)
this.cancelCanceled()
},
canceledAmount(historyElement) {
if (!this.isStronoEnabled(historyElement.timestamp)) {
return
}
this.showConfirmCanceledDialog = true
this.canceledMessage = historyElement
},
cancelCanceled() {
this.showConfirmCanceledDialog = null
this.canceledMessage = null
},
},
computed: {
...mapGetters({
user: 'user/user'
}),
...mapGetters('freeDrinkList', [
'free_drink_list_config_workgroup',
'free_drink_list_history',
'loading',
'free_drink_list_reasons',
'free_drink_list_reasons_loading',
'free_drink_list_history_workgroup_without_canceled',
'free_drink_list_history_workgroup',
'snackbar_messages'
]),
now() {
return now => {
var actual = new Date()
var zero = new Date(0)
var date = new Date(actual - now)
if (date.getFullYear() === zero.getFullYear()) {
if (date.getMonth() === zero.getMonth()) {
if (date.getDate() === zero.getDate()) {
if (date.getHours() === zero.getDate()) {
if (date.getMinutes() < 1) {
return 'vor ' + date.getSeconds() + ' Sekunden'
} else if (date.getMinutes() < 10) {
return 'vor ' + date.getMinutes() + ' Minuten'
} else {
return (
(now.getHours() < 10 ? '0' : '') +
now.getHours() +
':' +
(now.getMinutes() < 10 ? '0' : '') +
now.getMinutes()
)
}
} else {
return (
(now.getHours() < 10 ? '0' : '') +
now.getHours() +
':' +
(now.getMinutes() < 10 ? '0' : '') +
now.getMinutes()
)
}
}
}
}
return (
now.getDate() +
'.' +
now.getMonth() +
'.' +
now.getFullYear() +
' ' +
(now.getHours() < 10 ? '0' : '') +
now.getHours() +
':' +
(now.getMinutes() < 10 ? '0' : '') +
now.getMinutes()
)
}
},
isStronoEnabled() {
return now => {
var actual = new Date()
return actual - now < 60000
}
},
first_loading() {
return (
this.loading &&
this.free_drink_list_history.length == 0 &&
this.free_drink_list_config_workgroup.length == 0
)
}
},
beforeDestroy() {
clearInterval(this.timer)
}
}
</script>
<style scoped>
.drinkBtn {
width: 100%;
}
.drinkCol {
padding: 6px !important;
}
.title {
width: calc(100% - 135px);
min-width: 150px;
font-size: 1.25rem !important;
font-weight: 500;
line-height: 2rem;
letter-spacing: 0.0125em !important;
font-family: 'Roboto', sans-serif !important;
}
.menuBtn {
right: 15px;
position: absolute;
}
.history-item {
cursor: pointer;
}
</style>

View File

@ -1,138 +0,0 @@
<template>
<div>
<v-bottom-navigation v-model="bottom_nav" horizontal>
<v-btn link :to="{ name: 'freeDrinkListMain' }">
<span>Übersicht</span>
</v-btn>
<v-btn link :to="{name: 'freeDrinkListJob'}">
<span>Dienstgetränke</span>
</v-btn>
<v-btn link :to="{name: 'freeDrinkListWorkgroup'}">
<span>AG-Getränke</span>
</v-btn>
<v-btn link :to="{name: 'freeDrinkListBand'}">
<span>Bandgetränke</span>
</v-btn>
<v-btn link :to="{ name: 'freeDrinkListConfig' }">
<span>Einstellungen</span>
<v-icon>mdi-cogs</v-icon>
</v-btn>
</v-bottom-navigation>
<v-toolbar v-if="$route.name != 'freeDrinkListConfig'" flat dense>
<v-spacer/>
<v-toolbar-items>
<v-btn icon @click="change_month(-1)">
<v-icon>mdi-chevron-left</v-icon>
</v-btn>
<v-item>
<v-list-item-title>
{{month[date.getMonth()]}} {{date.getFullYear()}}
</v-list-item-title>
</v-item>
<v-btn icon @click="change_month(1)">
<v-icon>mdi-chevron-right</v-icon>
</v-btn>
</v-toolbar-items>
<v-spacer/>
</v-toolbar>
<v-progress-linear indeterminate v-if="loading" />
<div>
<router-view />
</div>
</div>
</template>
<script>
import { mapActions, mapGetters } from 'vuex'
export default {
name: 'FreeDrinkList',
data() {
return {
bottom_nav: true,
date: new Date(),
month: [
'Januar',
'Februar',
'März',
'April',
'Mai',
'Juni',
'Juli',
'August',
'September',
'Oktober',
'November',
'Dezember'
]
}
},
created() {
this.get_free_drink_list_reasons()
this.get_free_drink_list_config()
this.get_free_drink_list_history_from_to({
from_date: {
year: this.from_date.getFullYear(),
month: this.from_date.getMonth() + 1,
day: 1
},
to_date: {
year: this.to_date.getFullYear(),
month: this.to_date.getMonth() + 1,
day: 1
}
})
this.get_free_drink_types()
this.getPriceList()
},
methods: {
...mapActions('freeDrinkList', [
'get_free_drink_list_config',
'get_free_drink_list_history_from_to',
'get_free_drink_list_reasons',
'get_free_drink_types',
]),
...mapActions('priceList', ['getPriceList']),
change_month(payload) {
this.date = new Date(this.date.getFullYear(), this.date.getMonth() + payload, 1)
}
},
computed: {
...mapGetters('freeDrinkList', [
'free_drink_list_config_loading',
'free_drink_list_history_loading',
'free_drink_list_reasons_loading',
'free_drink_types_loading'
]),
...mapGetters('priceList', [
'priceListLoading'
]),
to_date() {
return new Date(this.date.getFullYear(), this.date.getMonth()+1,1)
},
from_date() {
return new Date(this.date.getFullYear(), this.date.getMonth(), 1)
},
loading() {
return this.free_drink_types_loading || this.free_drink_list_reasons_loading || this.free_drink_list_history_loading || this.free_drink_list_config_loading || this.priceListLoading
}
},
watch: {
date() {
this.get_free_drink_list_history_from_to({
from_date: {
year: this.from_date.getFullYear(),
month: this.from_date.getMonth() + 1,
day: 1
},
to_date: {
year: this.to_date.getFullYear(),
month: this.to_date.getMonth() + 1,
day: 1
}
})
}
}
}
</script>
<style scoped></style>

View File

@ -1,106 +0,0 @@
<template>
<div>
<v-row justify="space-around">
<v-card>
<v-card-title class="text-center">
Anzahl Freigetränke
</v-card-title>
<v-card-text class="text-h2 text-center">
{{free_drink_list_history_band_without_canceled.length}}
</v-card-text>
</v-card>
<v-card>
<v-card-title class="text-center">
Summe Freigetränke
</v-card-title>
<v-card-text class="text-h2 text-center">
{{ (sum/100).toFixed(2)}}
</v-card-text>
</v-card>
</v-row>
<v-data-table :headers="header" :items="table"/>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
name: "FreeDrinkListBand",
data() {
return {
header: [
{
text: 'Datum',
value: 'date'
},
{
text: 'Label Freigetränk',
value: 'label'
},
{
text: 'Freigetränk',
value: 'name'
},
{
text: 'Anzahl',
value: 'count'
},
{
text: 'Preis pro Getränk',
value: 'pricepro'
},
{
text: 'Preis gesamt',
value: 'sum'
}
]
}
},
computed: {
...mapGetters('freeDrinkList', ['free_drink_list_history_band_without_canceled', 'free_drink_list_config']),
sum() {
let sum = 0
this.free_drink_list_history_band_without_canceled.forEach(item => {
sum += item.free_drink_config.price
})
return sum
},
table() {
if (this.free_drink_list_history_band_without_canceled.length == 0) {
return []
}
let retVal = []
const date_month = this.free_drink_list_history_band_without_canceled[0].timestamp
let days = new Date(date_month.getFullYear(), date_month.getMonth() + 1, 0).getDate()
for (let day = 1; day <= days; day++) {
let from = new Date(date_month.getFullYear(), date_month.getMonth(), day)
let to = new Date(date_month.getFullYear(), date_month.getMonth(), day + 1)
let history_of_date = this.free_drink_list_history_band_without_canceled.filter(item => {
return item.timestamp >= from && item.timestamp <= to && !item.canceled
})
this.free_drink_list_config.forEach(drink_config => {
let history_of_config = history_of_date.filter(item => {
return item.free_drink_config_id == drink_config.id
})
if (history_of_config.length > 0) {
retVal.push({
date: `${from.getDate()}.${from.getMonth() + 1}.${from.getFullYear()}`,
label: drink_config.label,
name: drink_config.drink.name,
count: history_of_config.length,
pricepro: (drink_config.price / 100).toFixed(2),
sum: (drink_config.price / 100 * history_of_config.length).toFixed(2)
})
}
})
}
return retVal
}
}
}
</script>
<style scoped>
</style>

View File

@ -1,212 +0,0 @@
<template>
<div>
<v-card tile>
<v-card-title>
Freigetränke
</v-card-title>
<v-card-text>
<v-list>
<template
v-for="(free_drink_config, index) in free_drink_list_config">
<v-list-item
:key="free_drink_config.label"
dense
>
<v-list-item-content>
<FreeDrinkListConfigConfigItem :free_drink_config="free_drink_config" @save="save" @delete_free_drink_list_config="delete_free_drink_config" />
</v-list-item-content>
</v-list-item>
<v-divider
v-if="(index + 1 < free_drink_list_config.length) || new_free_drink_list_config"
:key="index"
/>
</template>
<v-list-item v-if="new_free_drink_list_config">
<v-list-item-content>
<FreeDrinkListConfigConfigItem :free_drink_config="default_model" @save="save" @delete_free_drink_config="new_free_drink_list_config=false"/>
</v-list-item-content>
</v-list-item>
</v-list>
</v-card-text>
<v-card-actions dense>
<v-spacer />
<v-btn fab small color="success" @click="new_free_drink_list_config=true" v-if="!new_free_drink_list_config">
<v-icon>mdi-plus</v-icon>
</v-btn>
</v-card-actions>
</v-card>
<v-card tile>
<v-card-title>Gründe für Freigetränke</v-card-title>
<v-card-text>
<v-list>
<template
v-for="(free_drink_reason, index) in free_drink_list_reasons">
<v-list-item
:key="free_drink_reason.name"
dense
>
<v-list-item-content>
<FreeDrinkListConfigReasonItem :free_drink_reason="free_drink_reason" @save="save_reason" @delete_free_drink_list_reason="delete_free_drink_reason" />
</v-list-item-content>
</v-list-item>
<v-divider
v-if="(index + 1 < free_drink_list_reasons.length) || new_free_drink_list_reason"
:key="index"
/>
</template>
<v-list-item v-if="new_free_drink_list_reason">
<v-list-item-content>
<FreeDrinkListConfigReasonItem :free_drink_reason="default_model_reason" @save="save_reason" @delete_free_drink_reason="new_free_drink_list_reason=false"/>
</v-list-item-content>
</v-list-item>
</v-list>
</v-card-text>
<v-card-actions dense>
<v-spacer />
<v-btn fab small color="success" @click="new_free_drink_list_reason=true" v-if="!new_free_drink_list_reason">
<v-icon>mdi-plus</v-icon>
</v-btn>
</v-card-actions>
</v-card>
<v-dialog v-model="check_delete_free_drink_list_config" max-width="400">
<v-card>
<v-card-title>
Wirklich löschen?
</v-card-title>
<v-card-text>
Willst du wirklich <span class="font-weight-black">{{check_model.label}}</span> mit dem Getränk <span class="font-weight-black">{{check_model.drink.name}}</span> löschen?
Wenn du dies löscht, wird auch der ganze Verlauf gelöscht. Dh. es ist nicht mehr vollständig nachvollziehbar, was getrunken wurde.
</v-card-text>
<v-card-actions>
<v-spacer/>
<v-btn @click="cancel_delete()">Abbrechen</v-btn>
<v-btn @click="accept_delete()" color="error">Löschen</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<v-dialog v-model="check_delete_free_drink_list_reason" max-width="400">
<v-card>
<v-card-title>
Wirklich löschen?
</v-card-title>
<v-card-text>
Willst du wirklich den Grund <span class="font-weight-black">{{check_model_reason.name}}</span> löschen?
Wenn du dies löscht, wird auch der im Verlauf alle dieser Gründe gelöscht.. Dh. es ist nicht mehr vollständig nachvollziehbar, aus welchem Grund ein Freigetränk rausgegeben wurde.
</v-card-text>
<v-card-actions>
<v-spacer/>
<v-btn @click="cancel_delete_reason()">Abbrechen</v-btn>
<v-btn @click="accept_delete_reason()" color="error">Löschen</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</template>
<script>
import { mapGetters, mapActions} from 'vuex'
import FreeDrinkListConfigConfigItem
from '@/components/vorstand/FreeDrinkList/FreeDrinkListConfigComponents/FreeDrinkListConfigConfigItem'
import FreeDrinkListConfigReasonItem
from '@/components/vorstand/FreeDrinkList/FreeDrinkListConfigComponents/FreeDrinkListConfigReasonItem'
export default {
name: 'FreeDrinkListConfig',
components: { FreeDrinkListConfigConfigItem, FreeDrinkListConfigReasonItem },
data() {
return {
new_free_drink_list_config: false,
new_free_drink_list_reason: false,
default_model: {
id: -1,
label: null,
free_drink_types: null,
drink: null,
price: null
},
check_model: {
label: null,
drink: {name: null}
},
default_check_model: {
label: null,
drink: {name: null}
},
check_delete_free_drink_list_config: false,
check_delete_free_drink_list_reason: false,
default_model_reason: {
id: -1,
name: null
},
check_model_reason: {
name: null
},
default_check_model_reason: {
name: null
}
}
},
methods: {
...mapActions('freeDrinkList', ['set_free_drink_list_config', 'delete_free_drink_list_config', 'update_free_drink_list_config', 'set_free_drink_list_reason', 'update_free_drink_list_reason', 'delete_free_drink_list_reason']),
log() {
console.log(this.free_drink_list_config)
},
reset(free_drink_config) {
let data = this.model.find(item => {
return item.id == free_drink_config.id
})
this.set_free_drink_config(data.origin)
this.model.splice(this.model.indexOf(data), 1)
},
cancel_delete() {
this.check_delete_free_drink_list_config = false
this.check_model = Object.assign({}, this.default_check_model)
},
accept_delete() {
this.delete_free_drink_list_config(this.check_model)
this.cancel_delete()
},
delete_free_drink_config(free_drink_config) {
Object.assign(this.check_model, free_drink_config)
this.check_delete_free_drink_list_config = true
console.log(this.check_delete_free_drink_list_config)
},
save(free_drink_config) {
if (free_drink_config.id > 0) {
this.update_free_drink_list_config(free_drink_config)
} else {
this.set_free_drink_list_config(free_drink_config)
}
},
delete_free_drink_reason(free_drink_reason) {
Object.assign(this.check_model_reason, free_drink_reason)
this.check_delete_free_drink_list_reason = true
},
save_reason(free_drink_reason) {
if (free_drink_reason.id > 0) {
this.update_free_drink_list_reason(free_drink_reason)
} else {
this.set_free_drink_list_reason(free_drink_reason)
}
},
cancel_delete_reason() {
this.check_delete_free_drink_list_reason = false
this.check_model_reason = Object.assign({}, this.default_check_model_reason)
},
accept_delete_reason() {
this.delete_free_drink_list_reason(this.check_model_reason)
this.cancel_delete_reason()
},
},
computed: {
...mapGetters('freeDrinkList', [
'free_drink_list_config',
'free_drink_list_reasons'
])
}
}
</script>
<style scoped></style>

View File

@ -1,120 +0,0 @@
<template>
<div>
<v-form :ref="free_drink_config.id">
<v-row>
<v-col cols="12" sm="6" md="3">
<v-text-field
outlined
label="Label"
:value="free_drink_config.label"
@change="set_model($event, 'label')"
dense
/>
</v-col>
<v-col cols="12" sm="6" md="3">
<v-autocomplete
outlined
label="Zugeordnetes Getränk"
:items="priceList"
:item-text="item => {return item.name + '/' + (item.price_club/100).toFixed(2) + '€'}"
item-value="id"
:value="free_drink_config.drink"
@change="set_model($event, 'drink')"
return-object
dense
/>
</v-col>
<v-col cols="12" sm="6" md="3">
<v-autocomplete
outlined
label="Freigetränkaufnahme"
multiple
:items="free_drink_types"
item-text="name"
item-value="id"
:value="free_drink_config.free_drink_types"
return-object
@change="set_model($event, 'free_drink_types')"
dense
/>
</v-col>
<v-col cols="12" sm="6" md="3">
<v-text-field
outlined
label="Preis in €"
type="number"
:value="(free_drink_config.price/100).toFixed(2)"
@change="set_model(Math.round($event*100), 'price')"
dense
/>
</v-col>
</v-row>
</v-form>
<v-row justify="end">
<v-btn
v-if="free_drink_config.id > 0"
icon
class="mr-3"
@click="$emit('delete_free_drink_list_config', free_drink_config)">
<v-icon>mdi-delete</v-icon>
</v-btn>
<v-btn
v-else
fab
color="error"
x-small
class="mr-4"
@click="$emit('delete_free_drink_config')"
>
<v-icon>mdi-minus</v-icon>
</v-btn>
<v-btn
v-if="!is_same || free_drink_config.id < 0"
class="mr-3"
color="success"
@click="() => {$emit('save', model); $emit('delete_free_drink_config'); model = null}"
>
Save
</v-btn>
</v-row>
</div>
</template>
<script>
import {mapGetters} from 'vuex'
export default {
name: "FreeDrinkListConfigConfigItem",
props: ['free_drink_config'],
data() {
return {
model: null
}
},
methods: {
set_model(event, type) {
console.log(!!this.model)
if (!this.model) {
this.model = Object.assign({}, this.free_drink_config)
}
if (type == 'drink') {
this.model.drink_id = event.id
}
this.model[type] = event
console.log(this.model)
}
},
computed: {
...mapGetters('freeDrinkList', [
'free_drink_types']),
...mapGetters('priceList', ["priceList"]),
is_same() {
console.log(this.model ? 'yo': 'no')
return this.model ? JSON.stringify(this.model) == JSON.stringify(this.free_drink_config) : true
}
}
}
</script>
<style scoped>
</style>

View File

@ -1,70 +0,0 @@
<template>
<div>
<v-row>
<v-col>
<v-text-field
outlined
label="Name"
:value="free_drink_reason.name"
@change="set_model($event)"
dense />
</v-col>
</v-row>
<v-row justify="end">
<v-btn
v-if="free_drink_reason.id > 0"
icon
class="mr-3"
@click="$emit('delete_free_drink_list_reason', free_drink_reason)">
<v-icon>mdi-delete</v-icon>
</v-btn>
<v-btn
v-else
fab
color="error"
x-small
class="mr-4"
@click="$emit('delete_free_drink_reason')"
>
<v-icon>mdi-minus</v-icon>
</v-btn>
<v-btn
v-if="!is_same || free_drink_reason.id < 0"
class="mr-3"
color="success"
@click="() => {$emit('save', model); $emit('delete_free_drink_reason'); model = null}"
>
Save
</v-btn>
</v-row>
</div>
</template>
<script>
export default {
name: "FreeDrinkListConfigReason",
props: ['free_drink_reason'],
data() {
return {
model: null
}
},
methods: {
set_model(event) {
if (!this.model) {
this.model = Object.assign({}, this.free_drink_reason)
}
this.model.name = event
}
},
computed: {
is_same() {
return this.model ? JSON.stringify(this.model) == JSON.stringify(this.free_drink_reason): true
}
}
}
</script>
<style scoped>
</style>

View File

@ -1,106 +0,0 @@
<template>
<div>
<v-row justify="space-around">
<v-card>
<v-card-title class="text-center">
Anzahl Freigetränke
</v-card-title>
<v-card-text class="text-h2 text-center">
{{free_drink_list_history_job_without_canceled.length}}
</v-card-text>
</v-card>
<v-card>
<v-card-title class="text-center">
Summe Freigetränke
</v-card-title>
<v-card-text class="text-h2 text-center">
{{ (sum/100).toFixed(2)}}
</v-card-text>
</v-card>
</v-row>
<v-data-table :headers="header" :items="table"/>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
name: "FreeDrinkListJob",
data() {
return {
header: [
{
text: 'Datum',
value: 'date'
},
{
text: 'Label Freigetränk',
value: 'label'
},
{
text: 'Freigetränk',
value: 'name'
},
{
text: 'Anzahl',
value: 'count'
},
{
text: 'Preis pro Getränk',
value: 'pricepro'
},
{
text: 'Preis gesamt',
value: 'sum'
}
]
}
},
computed: {
...mapGetters('freeDrinkList', ['free_drink_list_history_job_without_canceled', 'free_drink_list_config']),
sum() {
let sum = 0
this.free_drink_list_history_job_without_canceled.forEach(item => {
sum += item.free_drink_config.price
})
return sum
},
table() {
if (this.free_drink_list_history_job_without_canceled.length == 0) {
return []
}
let retVal = []
const date_month = this.free_drink_list_history_job_without_canceled[0].timestamp
let days = new Date(date_month.getFullYear(), date_month.getMonth() + 1, 0).getDate()
for (let day = 1; day <= days; day++) {
let from = new Date(date_month.getFullYear(), date_month.getMonth(), day)
let to = new Date(date_month.getFullYear(), date_month.getMonth(), day + 1)
let history_of_date = this.free_drink_list_history_job_without_canceled.filter(item => {
return item.timestamp >= from && item.timestamp <= to && !item.canceled
})
this.free_drink_list_config.forEach(drink_config => {
let history_of_config = history_of_date.filter(item => {
return item.free_drink_config_id == drink_config.id
})
if (history_of_config.length > 0) {
retVal.push({
date: `${from.getDate()}.${from.getMonth() + 1}.${from.getFullYear()}`,
label: drink_config.label,
name: drink_config.drink.name,
count: history_of_config.length,
pricepro: (drink_config.price / 100).toFixed(2),
sum: (drink_config.price / 100 * history_of_config.length).toFixed(2)
})
}
})
}
return retVal
}
}
}
</script>
<style scoped>
</style>

View File

@ -1,166 +0,0 @@
<template>
<div>
<v-row justify="space-around">
<v-card>
<v-card-title class="text-center">
Anzahl Freigetränke
</v-card-title>
<v-card-text class="text-h2 text-center">
{{free_drink_list_history_without_canceled.length}}
</v-card-text>
<v-card-title class="text-center">
Summe Freigetränke
</v-card-title>
<v-card-text class="text-h2 text-center">
{{ (sum/100).toFixed(2)}}
</v-card-text>
</v-card>
<v-card>
<v-card-title class="text-center">
Anzahl Dienstgetränke
</v-card-title>
<v-card-text class="text-h2 text-center">
{{ free_drink_list_history_job_without_canceled.length }}
</v-card-text>
<v-card-title class="text-center">
Summe Dienstgetränke
</v-card-title>
<v-card-text class="text-h2 text-center">
{{(sumJob/100).toFixed(2)}}
</v-card-text>
</v-card>
<v-card>
<v-card-title class="text-center">
Anzahl AG-Getränke
</v-card-title>
<v-card-text class="text-h2 text-center">
{{ free_drink_list_history_workgroup_without_canceled.length }}
</v-card-text>
<v-card-title class="text-center">
Summe AG-Getränke
</v-card-title>
<v-card-text class="text-h2 text-center">
{{(sumWorkgroup/100).toFixed(2)}}
</v-card-text>
</v-card>
<v-card>
<v-card-title class="text-center">
Anzahl Bandgetränke
</v-card-title>
<v-card-text class="text-h2 text-center">
{{ free_drink_list_history_band_without_canceled.length }}
</v-card-text>
<v-card-title class="text-center">
Summe Bandgetränke
</v-card-title>
<v-card-text class="text-h2 text-center">
{{(sumBand/100).toFixed(2)}}
</v-card-text>
</v-card>
</v-row>
<v-data-table :headers="header" :items="table"/>
</div>
</template>
<script>
import {mapGetters} from 'vuex'
export default {
name: "FreeDrinkListMain",
data() {
return {
header: [
{
text: 'Datum',
value: 'date'
},
{
text: 'Label Freigetränk',
value: 'label'
},
{
text: 'Freigetränk',
value: 'name'
},
{
text: 'Anzahl',
value: 'count'
},
{
text: 'Preis pro Getränk',
value: 'pricepro'
},
{
text: 'Preis gesamt',
value: 'sum'
}
]
}
},
computed: {
...mapGetters('freeDrinkList', ['free_drink_list_history_without_canceled', 'free_drink_list_config', 'free_drink_list_history_job_without_canceled', 'free_drink_list_history_band_without_canceled', 'free_drink_list_history_workgroup_without_canceled']),
sum() {
let sum = 0
this.free_drink_list_history_without_canceled.forEach(item => {
sum += item.free_drink_config.price
})
return sum
},
sumJob() {
let sum = 0
this.free_drink_list_history_job_without_canceled.forEach(item => {
sum += item.free_drink_config.price
})
return sum
},
sumWorkgroup() {
let sum = 0
this.free_drink_list_history_workgroup_without_canceled.forEach(item => {
sum += item.free_drink_config.price
})
return sum
},
sumBand() {
let sum = 0
this.free_drink_list_history_band_without_canceled.forEach(item => {
sum += item.free_drink_config.price
})
return sum
},
table() {
if (this.free_drink_list_history_without_canceled.length == 0) {
return []
}
let retVal = []
const date_month = this.free_drink_list_history_without_canceled[0].timestamp
let days = new Date(date_month.getFullYear(), date_month.getMonth() + 1, 0).getDate()
for (let day = 1; day <= days; day++) {
let from = new Date(date_month.getFullYear(), date_month.getMonth(), day)
let to = new Date(date_month.getFullYear(), date_month.getMonth(), day + 1)
let history_of_date = this.free_drink_list_history_without_canceled.filter(item => {
return item.timestamp >= from && item.timestamp <= to && !item.canceled
})
this.free_drink_list_config.forEach(drink_config => {
let history_of_config = history_of_date.filter(item => {
return item.free_drink_config_id == drink_config.id
})
if (history_of_config.length > 0) {
retVal.push({
date: `${from.getDate()}.${from.getMonth() + 1}.${from.getFullYear()}`,
label: drink_config.label,
name: drink_config.drink.name,
count: history_of_config.length,
pricepro: (drink_config.price/100).toFixed(2),
sum: (drink_config.price/100*history_of_config.length).toFixed(2)
})
}
})
}
return retVal
}
}
}
</script>
<style scoped>
</style>

View File

@ -1,112 +0,0 @@
<template>
<div>
<v-row justify="space-around">
<v-card>
<v-card-title class="text-center">
Anzahl Freigetränke
</v-card-title>
<v-card-text class="text-h2 text-center">
{{free_drink_list_history_workgroup_without_canceled.length}}
</v-card-text>
</v-card>
<v-card>
<v-card-title class="text-center">
Summe Freigetränke
</v-card-title>
<v-card-text class="text-h2 text-center">
{{ (sum/100).toFixed(2)}}
</v-card-text>
</v-card>
</v-row>
<v-data-table :headers="header" :items="table"/>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
name: "FreeDrinkListWorkgroup",
data() {
return {
header: [
{
text: 'Datum',
value: 'date'
},
{
text: 'Label Freigetränk',
value: 'label'
},
{
text: 'Freigetränk',
value: 'name'
},
{
text: 'User',
value: 'user'
},
{
text: 'Preis',
value: 'pricepro'
},
{
text: 'Grund',
value: 'reason'
},
{
text: 'Beschreibung',
value: 'description'
}
]
}
},
computed: {
...mapGetters('freeDrinkList', ['free_drink_list_history_workgroup_without_canceled', 'free_drink_list_config']),
sum() {
let sum = 0
this.free_drink_list_history_workgroup_without_canceled.forEach(item => {
sum += item.free_drink_config.price
})
return sum
},
table() {
if (this.free_drink_list_history_workgroup_without_canceled.length == 0) {
return []
}
let retVal = []
const date_month = this.free_drink_list_history_workgroup_without_canceled[0].timestamp
let days = new Date(date_month.getFullYear(), date_month.getMonth() + 1, 0).getDate()
for (let day = 1; day <= days; day++) {
let from = new Date(date_month.getFullYear(), date_month.getMonth(), day)
let to = new Date(date_month.getFullYear(), date_month.getMonth(), day + 1)
let history_of_date = this.free_drink_list_history_workgroup_without_canceled.filter(item => {
return item.timestamp >= from && item.timestamp <= to && !item.canceled
})
this.free_drink_list_config.forEach(drink_config => {
let history_of_config = history_of_date.filter(item => {
return item.free_drink_config_id == drink_config.id
})
console.log(history_of_config)
history_of_config.forEach(history_config => {
retVal.push({
date: `${from.getDate()}.${from.getMonth() + 1}.${from.getFullYear()}`,
label: drink_config.label,
name: drink_config.drink.name,
user: `${history_config.user.firstname} ${history_config.user.lastname}`,
reason: history_config.free_drink_list_reason ? history_config.free_drink_list_reason.name : null,
description: history_config.description,
pricepro: (drink_config.price / 100).toFixed(2)
})
})
})
}
return retVal
}
}
}
</script>
<style scoped>
</style>

View File

@ -1,171 +0,0 @@
<template>
<div>
<v-data-table
:items="jobkinds"
:headers="header"
:loading="jobkindsLoading || workgroupsLoading"
:search="search"
>
<template v-slot:top>
<v-toolbar flat color="white">
<v-toolbar-title>
Dienstarten
</v-toolbar-title>
<v-spacer />
<v-text-field
v-model="search"
label="Suche Dienstart"
single-line
hide-details
>
<template v-slot:append>
<v-icon>{{ searchIcon }}</v-icon>
</template>
</v-text-field>
<v-btn fab small color="primary" @click="add()">
<v-icon>{{ plusIcon }}</v-icon>
</v-btn>
</v-toolbar>
</template>
<template v-slot:item.workgroup="{ item }">
{{ item.workgroup === null ? 'Alle' : item.workgroup.name }}
</template>
<template v-slot:item.actions="{item}">
<v-icon x-small @click="editJobKind(item)">{{editIcon}}</v-icon>
<v-icon x-small @click="deleteJobKind(item)">{{deleteIcon}}</v-icon>
</template>
</v-data-table>
<v-dialog v-model="dialog">
<v-card>
<v-card-title>
{{ title }}
</v-card-title>
<v-card-text>
<v-row>
<v-col>
<v-text-field outlined label="Name" v-model="editedItem.name" />
</v-col>
<v-col>
<v-autocomplete
outlined
return-object
label="Arbeitsgruppe"
v-model="editedItem.workgroup"
item-text="name"
item-value="id"
:items="[...workgroups, { id: -1, name: 'Alle' }]"
/>
</v-col>
</v-row>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn text color="primary" @click="close()">Abbrechen</v-btn>
<v-btn text color="primary" @click="save()">Speichern</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex'
import { mdiPencil, mdiDelete, mdiPlus, mdiMagnify } from '@mdi/js'
export default {
name: 'JobKindManager',
data() {
return {
search: null,
editIcon: mdiPencil,
deleteIcon: mdiDelete,
plusIcon: mdiPlus,
searchIcon: mdiMagnify,
dialog: false,
header: [
{ text: 'Name', value: 'name' },
{ text: 'Zugewiesene Arbeitsgruppe', value: 'workgroup' },
{
text: 'Aktionen',
value: 'actions',
filterable: false,
sortable: false
}
],
editedItem: {
id: -1,
name: null,
workgroup: {
id: -1,
name: null
}
},
defaultItem: {
id: -1,
name: null,
workgroup: {
id: -1,
name: null
}
}
}
},
methods: {
...mapActions({
getAllJobKinds: 'jkm/getAllJobKinds',
addingJobKind: 'jkm/addJobKind',
updateJobKind: 'jkm/updateJobKind',
deletingJobKind: 'jkm/deleteJobKind',
getAllWorkgroups: 'wm/getAllWorkgroups'
}),
add() {
this.dialog = true
},
editJobKind(jobkind) {
this.dialog = true
this.editedItem = Object.assign({}, jobkind)
if (this.editedItem.workgroup === null) {
this.editedItem.workgroup = Object.assign({}, this.defaultItem.workgroup)
}
},
deleteJobKind(jobkind) {
confirm('Willst du diese Dienstart wirklich löschen') &&
this.deletingJobKind(jobkind)
this.close()
},
close() {
setTimeout(() => {
this.editedItem = Object.assign({}, this.defaultItem)
this.dialog = false
}, 200)
},
save() {
if (this.editedItem.workgroup.id === -1)
this.editedItem.workgroup = null
this.editedItem.id === -1
? this.addingJobKind(this.editedItem)
: this.updateJobKind(this.editedItem)
this.close()
}
},
computed: {
...mapGetters({
jobkinds: 'jkm/jobkinds',
jobkindsLoading: 'jkm/jobkindsLoading',
workgroups: 'wm/workgroups',
workgroupsLoading: 'wm/workgroupLoading'
}),
title() {
return this.editedItem.id === -1
? 'Neue Dienstart erstellen'
: 'Dienstart bearbeiten'
}
},
created() {
this.getAllJobKinds()
this.getAllWorkgroups()
console.log(this.jobkinds)
}
}
</script>
<style scoped></style>

View File

@ -1,60 +0,0 @@
<template>
<v-list>
<v-list-item link :to="{name: 'serviceManagement', params: {year: new Date().getFullYear(), month: new Date().getMonth() + 1}}"> <v-list-item-icon>
<v-icon>{{ work }}</v-icon>
</v-list-item-icon>
<v-list-item-title>
Dienstverwaltung
</v-list-item-title>
</v-list-item>
<v-list-item link :to="{name: 'userManager'}">
<v-list-item-icon>
<v-icon>{{list}}</v-icon>
</v-list-item-icon>
<v-list-item-title>
Benutzerliste
</v-list-item-title>
</v-list-item>
<v-list-item link :to="{name: 'workgroupManagement'}">
<v-list-item-icon>
<v-icon>{{group}}</v-icon>
</v-list-item-icon>
<v-list-item-title>
Arbeitsgruppen
</v-list-item-title>
</v-list-item>
<v-list-item link :to="{name: 'jobkindManagement'}">
<v-list-item-icon>
<v-icon>{{jobs}}</v-icon>
</v-list-item-icon>
<v-list-item-title>
Dienstarten
</v-list-item-title>
</v-list-item>
<v-list-item link :to="{name: 'freeDrinkList'}">
<v-list-item-icon>
<v-icon>mdi-beer</v-icon>
</v-list-item-icon>
<v-list-item-title>
Freigetränke
</v-list-item-title>
</v-list-item>
</v-list>
</template>
<script>
import { mdiBriefcase, mdiAccountMultiple, mdiAccountGroup, mdiAccountNetwork } from '@mdi/js'
export default {
name: 'ManagementNavigation',
data() {
return {
work: mdiBriefcase,
list: mdiAccountMultiple,
group: mdiAccountGroup,
jobs: mdiAccountNetwork
}
}
}
</script>
<style scoped></style>

View File

@ -1,169 +0,0 @@
<template>
<div>
<v-toolbar>
<v-toolbar-title>Dienstverwaltung</v-toolbar-title>
<v-spacer />
<v-toolbar-items>
<v-btn text icon :to="{name: 'serviceManagement', params: {year: date.getFullYear(), month: date.getMonth()}}">
<v-icon>{{ keyboard_arrow_left }}</v-icon>
</v-btn>
<v-list-item>
<v-list-item-title class="title">
{{ monthArray[date.getMonth()] }}
{{ date.getFullYear() }}
</v-list-item-title>
</v-list-item>
<v-btn text icon :to="{name: 'serviceManagement', params: {year: date.getFullYear(), month: date.getMonth() + 2}}">
<v-icon>{{ keyboard_arrow_right }}</v-icon>
</v-btn>
</v-toolbar-items>
<v-spacer />
<v-toolbar-items>
<v-btn text @click="lockDays(true)">Monat sperren</v-btn>
<v-btn text @click="lockDays(false)">Monat freigeben</v-btn>
</v-toolbar-items>
</v-toolbar>
<v-card v-for="week in month" :key="month.indexOf(week)" tile flat>
<v-card-title class="subtitle-1 font-weight-bold">
Woche vom {{ week.startDate.getDate() }}.{{
week.startDate.getMonth() + 1
}}.{{ week.startDate.getFullYear() }} bis
{{ week.endDate.getDate() }}.{{ week.endDate.getMonth() + 1 }}.{{
week.endDate.getFullYear()
}}
</v-card-title>
<v-card-text>
<v-row justify="start" align="start">
<div v-for="day in week.days" :key="day.id">
<v-col>
<Day v-bind:day="day" />
</v-col>
</div>
</v-row>
</v-card-text>
</v-card>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex'
import { mdiChevronLeft, mdiChevronRight } from '@mdi/js'
import Day from './ServiceManagementComponents/Day'
export default {
name: 'ServiceManagement',
components: { Day },
data() {
return {
keyboard_arrow_left: mdiChevronLeft,
keyboard_arrow_right: mdiChevronRight,
id: 0,
date: new Date(this.$route.params.year, this.$route.params.month -1, 1),
monthArray: [
'Januar',
'Februar',
'März',
'April',
'Mai',
'Juni',
'Juli',
'August',
'September',
'Oktober',
'November',
'Dezember'
]
}
},
created() {
this.getAllJobKinds()
this.createMonth(this.date)
this.getAllUsers()
this.getDBUsers()
this.getUsers({
from_date: {
year: this.startDate.getFullYear(),
month: this.startDate.getMonth() + 1,
day: this.startDate.getDate()
},
to_date: {
year: this.endDate.getFullYear(),
month: this.endDate.getMonth() + 1,
day: this.endDate.getDate()
}
})
},
methods: {
...mapActions({
createMonth: 'sm/createMonth',
getAllUsers: 'sm/getAllUsers',
getUsers: 'sm/getUsers',
lockDay: 'sm/lockDay',
getDBUsers: 'usermanager/getUsersWithExtern',
getAllJobKinds: 'jkm/getAllJobKinds',
}),
changeMonth(value) {
if (value === -1) {
this.date = new Date(this.date.getFullYear(), this.date.getMonth() - 1)
} else {
this.date = new Date(this.date.getFullYear(), this.date.getMonth() + 1)
}
this.createMonth(this.date)
this.getUsers({
from_date: {
year: this.startDate.getFullYear(),
month: this.startDate.getMonth() + 1,
day: this.startDate.getDate()
},
to_date: {
year: this.endDate.getFullYear(),
month: this.endDate.getMonth() + 1,
day: this.endDate.getDate()
}
})
},
lockDays(value) {
for (var week in this.month) {
for (var dayint in this.month[week].days) {
var day = this.month[week].days[dayint]
this.lockDay({
year: day.date.getFullYear(),
month: day.date.getMonth() + 1,
day: day.date.getDate(),
locked: value
})
}
}
}
},
computed: {
...mapGetters({
month: 'sm/month',
startDate: 'sm/getStartDate',
endDate: 'sm/getEndDate'
})
},
watch: {
$route() {
this.date = new Date(this.$route.params.year, this.$route.params.month - 1, 1)
this.getAllJobKinds()
this.createMonth(this.date)
this.getAllUsers()
this.getDBUsers()
this.getUsers({
from_date: {
year: this.startDate.getFullYear(),
month: this.startDate.getMonth() + 1,
day: this.startDate.getDate()
},
to_date: {
year: this.endDate.getFullYear(),
month: this.endDate.getMonth() + 1,
day: this.endDate.getDate()
}
})
}
}
}
</script>
<style scoped></style>

View File

@ -1,518 +0,0 @@
<template>
<div v-if="day">
<v-card :color="color(day)" max-width="20em">
<v-card-title v-if="day.date" class="subtitle-1 font-weight-bold">
{{ day.name }} {{ day.date.getDate() }}.{{ day.date.getMonth() + 1 }}.{{
day.date.getFullYear()
}}
<v-spacer />
<v-btn icon small @click="dialog = true">
<v-icon>{{ menuIcon }}</v-icon>
</v-btn>
</v-card-title>
<v-card-text>
<v-expand-transition>
<v-row align="center" justify="center" v-if="day.loading">
<v-progress-circular indeterminate color="grey" />
</v-row>
</v-expand-transition>
<div v-for="(jobkinddateitem, index) in day.jobkinddate" :key="index">
<div
v-if="
jobkinddateitem.job_kind
? jobkinddateitem.job_kind.id !== 0
: false
"
>
<v-expand-transition>
<div v-show="!day.loading">
<v-autocomplete
chips
return-object
multiple
:counter="jobkinddateitem.maxpersons"
v-model="jobkinddateitem.worker"
:items="filterUser(jobkinddateitem.job_kind)"
:item-text="item => item.firstname + ' ' + item.lastname"
:label="jobkinddateitem.job_kind.name"
filled
color="green"
@blur="focused = false"
@focus="focused = true"
:key="update"
@change="forceRenderer(jobkinddateitem)"
>
<template v-slot:prepend-inner>
<v-icon>{{ account_add }}</v-icon>
</template>
<template v-slot:selection="data">
<v-chip
v-bind="data.attrs"
:input-value="data.selected"
close
@click="data.select"
@click:close="remove(jobkinddateitem, data.item)"
>{{ data.item.firstname }} {{ data.item.lastname }}
</v-chip>
</template>
</v-autocomplete>
</div>
</v-expand-transition>
</div>
</div>
</v-card-text>
<v-card-actions v-if="!day.loading">
<v-chip class="text-uppercase" :color="lockedColor">{{
lockedText
}}</v-chip>
<v-spacer />
<v-btn text @click="lock">{{ lockedTextBtn }}</v-btn>
</v-card-actions>
</v-card>
<v-dialog v-model="dialog">
<v-card>
<v-card-title>
Bearbeite Tag
</v-card-title>
<v-card-text>
<div>
<v-row v-if="!isBarDienstIn">
<v-col cols="8">
<v-text-field readonly outlined value="Bardienst" />
</v-col>
<v-col cols="2">
<v-text-field
outlined
label="Maximale Personen"
type="number"
v-model="maxpersons"
@change="createBarJobKindDate(maxpersons)"
/>
</v-col>
<v-col cols="2"> </v-col>
</v-row>
<v-row
v-for="(jobkinddateitem, index) in day.jobkinddate"
:key="index"
>
<v-col cols="8">
<v-text-field
v-if="!jobkinddateitem.new"
readonly
outlined
:value="jobkinddateitem.job_kind.name"
/>
<v-autocomplete
v-else
outlined
:items="filterJobKinds(jobkinddateitem, index)"
:rules="rules"
item-text="name"
item-value="id"
v-model="jobkinddateitem.job_kind"
return-object
></v-autocomplete>
</v-col>
<v-col cols="2">
<v-text-field
outlined
label="Maximale Personen"
type="number"
v-model="jobkinddateitem.maxpersons"
/>
</v-col>
<v-col cols="2">
<div v-if="jobkinddateitem.job_kind !== null">
<div v-if="jobkinddateitem.job_kind.id !== 1">
<v-btn
v-if="jobkinddateitem.id === 0"
fab
x-small
color="green"
@click="undoDelteJobKindDate(index)"
>
<v-icon>{{ plusIcon }}</v-icon>
</v-btn>
<v-btn
v-else
fab
x-small
color="red"
@click="deleteJobKindDate(index)"
>
<v-icon>{{ minusIcon }}</v-icon>
</v-btn>
</div>
</div>
<div v-else>
<v-btn
v-if="jobkinddateitem.id === 0"
fab
x-small
color="green"
@click="undoDelteJobKindDate(index)"
>
<v-icon>{{ plusIcon }}</v-icon>
</v-btn>
<v-btn
v-else
fab
x-small
color="red"
@click="deleteJobKindDate(index)"
>
<v-icon>{{ minusIcon }}</v-icon>
</v-btn>
</div>
</v-col>
<v-row v-if="jobkinddateitem.id === 0">
<v-col>
<v-alert dense type="info"
>{{ jobkinddateitem.job_kind.name }} wird beim Speichern
gelöscht.</v-alert
>
</v-col>
</v-row>
</v-row>
</div>
<v-row>
<v-spacer />
<v-btn
fab
small
color="green darken-1"
@click="addJobKindDate()"
:disabled="disableAddBtn"
>
<v-icon>{{ plusIcon }}</v-icon>
</v-btn>
</v-row>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn text color="primary" @click="dialog = false">
Abbrechen
</v-btn>
<v-btn text color="primary" @click="saveJobKind()">
Speichern
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex'
import { mdiAccountPlus, mdiDotsVertical, mdiPlus, mdiMinus } from '@mdi/js'
export default {
name: 'Day',
props: {
day: Object
},
data() {
return {
account_add: mdiAccountPlus,
menuIcon: mdiDotsVertical,
plusIcon: mdiPlus,
minusIcon: mdiMinus,
searchInput: null,
focused: false,
dialog: false,
update: 0,
maxpersons: 2,
rules: [
data => {
if (data === null) return false
var list = this.day.jobkinddate.filter(a => {
if (a.job_kind === null) return false
else {
return a.job_kind.id === data.id
}
})
return list.length > 1 ? data.name + 'ist schon vorhanden' : false
}
],
backup: null
}
},
created() {
this.setLoading(this.day.date)
},
methods: {
...mapActions({
addUser: 'sm/addUser',
deleteUser: 'sm/deleteUser',
setLoading: 'sm/setDayLoading',
setNotLoading: 'sm/setDayNotLoading',
lockDay: 'sm/lockDay',
updateJobKindDate: 'sm/updateJobKindDate'
}),
forceRenderer(jobkind) {
this.update += 1
if (jobkind.backupWorker !== jobkind.worker && this.focused) {
let addedUser = null
for (let user in jobkind.worker) {
if (!jobkind.backupWorker.includes(jobkind.worker[user])) {
addedUser = jobkind.worker[user]
this.addUser({
date: this.day.date.getTime() / 1000,
user: addedUser,
year: this.day.date.getFullYear(),
month: this.day.date.getMonth() + 1,
day: this.day.date.getDate(),
job_kind: jobkind.job_kind
})
}
}
let deletedUser = null
for (let user in jobkind.backupWorker) {
if (!jobkind.worker.includes(jobkind.backupWorker[user])) {
deletedUser = jobkind.backupWorker[user]
this.deleteUser({
startdatetime: this.day.date,
date: this.day.date.getTime() / 1000,
user: deletedUser,
year: this.day.date.getFullYear(),
month: this.day.date.getMonth() + 1,
day: this.day.date.getDate()
})
}
}
}
jobkind.backupWorker = [...jobkind.worker]
},
// eslint-disable-next-line no-unused-vars
remove(jobkind, deletedUser) {
jobkind.worker.indexOf()
const obj = jobkind.worker.find(a => {
return a.username === deletedUser.username
})
const index = jobkind.worker.indexOf(obj)
if (index >= 0) jobkind.worker.splice(index, 1)
this.forceRenderer(jobkind)
},
color(day) {
if (day) {
if (day.date.getDay() === 0 || day.date.getDay() === 1) {
return 'grey lighten-4'
} else {
var retVal = 'yellow'
retVal = 'light-green'
for (var jobkind in day.jobkinddate) {
if (
day.jobkinddate[jobkind].worker.length >=
day.jobkinddate[jobkind].maxpersons
)
retVal = 'light-green'
else return 'yellow'
}
return retVal
}
} else {
return 'grey lighten-4'
}
},
lock() {
this.lockDay({
year: this.day.date.getFullYear(),
month: this.day.date.getMonth() + 1,
day: this.day.date.getDate(),
locked: !this.day.locked
})
},
createBarJobKindDate(maxpersons) {
this.day.jobkinddate.push({
id: -1,
job_kind: Object.assign(
{},
this.jobkinds.find(a => {
return a.id === 1
})
),
maxpersons: maxpersons,
daydate: {
year: this.day.date.getFullYear(),
month: this.day.date.getMonth() + 1,
day: this.day.date.getDate()
},
worker: [],
backupWorker: []
})
},
addJobKindDate() {
this.day.jobkinddate.push({
id: -1,
job_kind: null,
maxpersons: 2,
new: true,
daydate: {
year: this.day.date.getFullYear(),
month: this.day.date.getMonth() + 1,
day: this.day.date.getDate()
},
worker: [],
backupWorker: []
})
},
mop(jobkind) {
console.log(jobkind.worker)
},
deleteJobKindDate(index) {
if (this.day.jobkinddate[index].id === -1) {
this.day.jobkinddate.splice(index, 1)
} else {
this.day.jobkinddate[index].backupid = this.day.jobkinddate[index].id
this.day.jobkinddate[index].id = 0
}
},
undoDelteJobKindDate(index) {
this.day.jobkinddate[index].id = this.day.jobkinddate[index].backupid
},
saveJobKind() {
this.updateJobKindDate({
data: this.day.jobkinddate,
date: this.day.date
})
this.dialog = false
},
filterUser(jobkind) {
var filtered = this.dbUsers.filter(user => {
var userInOther = this.day.jobkinddate.find(item => {
if (item.job_kind === null) {
return false
}
if (item.job_kind.id === jobkind.id) {
return false
}
return item.worker.find(work => {
return work.id === user.id
})
})
if (userInOther) {
return false
}
if (jobkind.id === 1 || !jobkind.workgroup) {
return true
} else {
if (user.workgroups ? user.workgroups.length > 0 : false) {
return user.workgroups.find(wg => {
return wg.id === jobkind.workgroup.id
})
} else {
return false
}
}
})
return filtered
},
// eslint-disable-next-line no-unused-vars
filterJobKinds(jobkinddateitem, index) {
var retVal = this.jobkinds.filter(jobkind => {
return jobkind.id !== 1
})
retVal = retVal.filter(jobkind => {
return !this.day.jobkinddate.find(item => {
if (item.job_kind === jobkinddateitem.job_kind) {
return false
} else {
if (item.job_kind !== null && jobkind !== null)
if (item.job_kind.id === jobkind.id) return true
}
return false
})
})
return retVal
}
},
computed: {
...mapGetters({
allUsers: 'sm/allUsers',
dbUsers: 'usermanager/users',
disabled: 'sm/disabled',
jobkinds: 'jkm/jobkinds'
}),
worker() {
return this.day.worker
},
tada() {
return this.day.jobkinddate
},
lockedColor() {
return this.day.locked ? 'red' : 'green'
},
lockedText() {
return this.day.locked ? 'gesperrt' : 'frei'
},
lockedTextBtn() {
return this.day.locked ? 'freigeben' : 'sperren'
},
isBarDienstIn() {
for (var jobkinddate in this.day.jobkinddate) {
if (!(this.day.jobkinddate[jobkinddate].job_kind === null))
if (this.day.jobkinddate[jobkinddate].job_kind.id === 1) return true
}
return false
},
existJobKinds() {
var retVal = [...this.jobkinds]
retVal.splice(
retVal.findIndex(a => {
return a.id === 1
}),
1
)
return retVal
},
disableAddBtn() {
var barset = this.isBarDienstIn ? 0 : 1
return this.day.jobkinddate.length === this.jobkinds.length - barset
}
},
watch: {
worker(newValue, oldValue) {
if (oldValue !== newValue && this.focused) {
let addedUser = null
for (let user in newValue) {
if (!oldValue.includes(newValue[user])) {
addedUser = newValue[user]
this.addUser({
date: this.day.date.getTime() / 1000,
user: addedUser,
year: this.day.date.getFullYear(),
month: this.day.date.getMonth() + 1,
day: this.day.date.getDate()
})
}
}
let deletedUser = null
for (let user in oldValue) {
if (!newValue.includes(oldValue[user])) {
deletedUser = oldValue[user]
this.deleteUser({
startdatetime: this.day.date,
date: this.day.date.getTime() / 1000,
user: deletedUser,
year: this.day.date.getFullYear(),
month: this.day.date.getMonth() + 1,
day: this.day.date.getDate()
})
}
}
}
},
dialog(newValue) {
if (newValue) {
this.backup = [...this.day.jobkinddate]
} else {
this.day.jobkinddate = [...this.backup]
this.backup = []
}
}
}
}
</script>
<style scoped></style>

View File

@ -1,320 +0,0 @@
<template>
<div>
<v-dialog v-model="editUser">
<v-card>
<v-card-title>
{{
this.editedItem.firstname +
' ' +
this.editedItem.lastname +
' bearbeiten'
}}
</v-card-title>
<v-card-text>
<v-container>
<v-row>
<v-col>
<v-autocomplete
v-model="editedItem.statusgroup"
label="Status"
outlined
:items="status"
item-value="id"
item-text="name"
return-object
@change="clickItem(editedItem.statusgroup)"
>
</v-autocomplete>
</v-col>
<v-col>
<v-autocomplete
v-model="editedItem.voting"
label="Stimmrecht"
outlined
:items="[
{ value: true, text: 'ja' },
{ value: false, text: 'nein' }
]"
item-text="text"
item-value="value"
:disabled="disableVoting"
return-object
/>
</v-col>
</v-row>
<v-row>
<v-col>
<v-autocomplete
chips
multiple
v-model="editedItem.workgroups"
label="AG's"
outlined
:items="workgroups"
item-value="id"
item-text="name"
return-object
></v-autocomplete>
</v-col>
</v-row>
</v-container>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn color="primary" text @click="close()">Abbrechen</v-btn>
<v-btn color="primary" text @click="save()">Speichern</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<v-data-table
:headers="header"
:items="users"
:search="search"
:loading="usersLoading || statusLoading"
:items-per-page="100"
>
<template v-slot:top>
<v-toolbar flat color="white">
<v-toolbar-title>Mitgliederliste</v-toolbar-title>
<v-spacer></v-spacer>
<v-text-field
v-model="search"
label="Sucher Mitglied"
single-line
hide-details
>
<template v-slot:append>
<v-icon>{{ searchIcon }}</v-icon>
</template>
</v-text-field>
</v-toolbar>
</template>
<template v-slot:item.workgroups="{ item }">
<div>
<v-chip v-for="group in item.workgroups" :key="group.id" x-small>
{{ group.name }}
</v-chip>
</div>
</template>
<template v-slot:item.statusgroup="{ item }">
{{ computeStatus(item.statusgroup) }}
</template>
<template v-slot:item.voting="{ item }">
<v-chip small :color="item.voting ? 'green' : 'red'">
{{ item.voting ? 'ja' : 'nein' }}
</v-chip>
</template>
<template v-slot:item.actions="{ item }">
<v-icon small @click="editItem(item)">{{ editIcon }}</v-icon>
</template>
</v-data-table>
<v-divider />
<v-card>
<v-card-text>
<v-container>
<v-row>
<v-col v-bind:class="{ fulllineText: isFulllineText }">
<v-text-field
outlined
:value="users.length"
label="Anzahl aller Mitglieder"
readonly
/>
</v-col>
<v-col v-bind:class="{ fulllineText: isFulllineText }">
<v-text-field
outlined
:value="allActiveUsers"
label="Anzahl aller aktiven Mitglieder"
readonly
/>
</v-col>
<v-col v-bind:class="{ fulllineText: isFulllineText }">
<v-text-field
outlined
:value="allVotings"
label="Anzahl aller Stimmberechtigten"
readonly
/>
</v-col>
</v-row>
</v-container>
</v-card-text>
</v-card>
</div>
</template>
<script>
import { mdiPencil, mdiMagnify } from '@mdi/js'
import { mapActions, mapGetters } from 'vuex'
export default {
name: 'UserManager',
data() {
return {
editIcon: mdiPencil,
searchIcon: mdiMagnify,
isFulllineText: false,
editUser: false,
disableVoting: null,
search: null,
header: [
{ text: 'Nachname', value: 'lastname' },
{ text: 'Vorname(n)', value: 'firstname' },
{ text: "AG's", value: 'workgroups' },
{ text: 'Status', value: 'statusgroup' },
{ text: 'Stimmrecht', value: 'voting' },
{
text: 'Aktionen',
value: 'actions',
sortable: false,
filterable: false
}
],
editedItem: {
id: -1,
firstname: null,
lastname: null,
username: null,
workgroups: [],
statusgroup: {
id: -1,
name: null
},
voting: {
value: false,
text: 'nein'
}
},
defaultItem: {
id: -1,
username: null,
workgroups: [],
statusgroup: {
id: -1,
name: null
},
voting: {
value: false,
text: 'nein'
}
}
}
},
mounted() {
this.$nextTick(function() {
window.addEventListener('resize', this.getWindowWidth)
this.getWindowWidth()
})
},
methods: {
...mapActions({
getUsers: 'usermanager/getUsers',
getStatus: 'usermanager/getStatus',
updateStatusUser: 'usermanager/updateStatusUser',
updateVoting: 'usermanager/updateVoting',
updateWorkgroups: 'usermanager/updateWorkgroups',
getAllWorkgroups: 'wm/getAllWorkgroups'
}),
getWindowWidth() {
this.isFulllineText = document.documentElement.clientWidth <= 750
},
close() {
this.editUser = false
setTimeout(() => {
this.editedItem = Object.assign({}, this.defaultItem)
}, 300)
},
editItem(item) {
this.editedItem = Object.assign({}, item)
this.editedItem.statusgroup = Object.assign(
{},
this.status.find(a => a.id == item.statusgroup)
)
this.editedItem.voting = Object.assign(
{},
item.voting
? { value: true, text: 'ja' }
: { value: false, text: 'nein' }
)
this.clickItem(this.editedItem.statusgroup)
this.editUser = true
},
clickItem(item) {
switch (item.id) {
case 1:
this.editedItem.voting = { value: true, text: 'ja' }
this.disableVoting = true
break
case 2:
this.disableVoting = false
break
case 3:
this.disableVoting = true
this.editedItem.voting = { value: false, text: 'nein' }
break
case 4:
this.editedItem.voting = { value: false, text: 'nein' }
this.disableVoting = true
break
case 5:
this.editedItem.voting = { value: false, text: 'nein' }
this.disableVoting = true
break
}
},
save() {
this.updateStatusUser({
username: this.editedItem.username,
status: this.editedItem.statusgroup
})
this.updateVoting({
username: this.editedItem.username,
voting: this.editedItem.voting.value
})
this.updateWorkgroups(this.editedItem)
this.close()
}
},
computed: {
...mapGetters({
users: 'usermanager/users',
status: 'usermanager/status',
usersLoading: 'usermanager/usersLoading',
statusLoading: 'usermanager/statusLoading',
workgroups: 'wm/workgroups'
}),
computeStatus() {
return id => {
let status = this.status.find(a => {
return a.id === id
})
return status ? status.name : null
}
},
allVotings() {
return this.users.filter(a => {
return a.voting
}).length
},
allActiveUsers() {
return this.users.filter(a => {
return a.statusgroup === 1
}).length
}
},
created() {
this.getUsers()
this.getStatus()
this.getAllWorkgroups()
}
}
</script>
<style scoped>
.fulllineText {
flex-basis: unset;
}
</style>

View File

@ -1,190 +0,0 @@
<template>
<div>
<v-data-table
:headers="header"
:items="workgroups"
:loading="workgroupLoading"
:search="search"
>
<template v-slot:top>
<v-toolbar flat color="white">
<v-toolbar-title>Arbeitsgruppen</v-toolbar-title>
<v-spacer> </v-spacer>
<v-text-field
v-model="search"
label="Suche Arbeitsgrupp"
single-line
hide-details
>
<template v-slot:append>
<v-icon>{{ searchIcon }}</v-icon>
</template>
</v-text-field>
<v-dialog v-model="dialog">
<template v-slot:activator="{ on }">
<v-btn
fab
x-small
color="primary"
class="mb-2"
v-on="on"
style="margin: 5px"
>
<v-icon>
{{ plusIcon }}
</v-icon>
</v-btn>
</template>
<v-card>
<v-card-title>
<span class="headline">{{ formTitle }}</span>
</v-card-title>
<v-card-text>
<v-container>
<v-text-field
v-model="editedItem.name"
label="Name"
outlined
></v-text-field>
<v-autocomplete
v-model="editedItem.boss"
label="AG-Leiter"
:items="users"
:item-text="item => item.firstname + ' ' + item.lastname"
item-value="username"
:loading="usersLoading"
outlined
return-object
>
</v-autocomplete>
</v-container>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="blue darken-1" text @click="close"
>Abbreche</v-btn
>
<v-btn color="blue darken-1" text @click="save"
>Speichern</v-btn
>
</v-card-actions>
</v-card>
</v-dialog>
</v-toolbar>
</template>
<template v-slot:item.boss="{ item }">
<div v-if="item.boss != null">
{{ item.boss.firstname }} {{ item.boss.lastname }}
</div>
</template>
<template v-slot:item.actions="{ item }">
<v-icon small class="mr-2" @click="editItem(item)">
{{ editIcon }}
</v-icon>
<v-icon small class="mr-2" @click="deleteItem(item)">
{{ deleteIcon }}
</v-icon>
</template>
</v-data-table>
</div>
</template>
<script>
import { mdiPencil, mdiMagnify, mdiDelete, mdiPlus } from '@mdi/js'
import { mapActions, mapGetters } from 'vuex'
export default {
name: 'WorkgroupManagement',
data() {
return {
editIcon: mdiPencil,
searchIcon: mdiMagnify,
deleteIcon: mdiDelete,
plusIcon: mdiPlus,
search: null,
dialog: false,
header: [
{ text: 'AG-Name', value: 'name' },
{ text: 'AG-Leiter', value: 'boss' },
{
text: 'Aktionen',
value: 'actions',
sortable: false,
filterable: false
}
],
editedItem: {
id: -1,
username: null,
boss: {
username: null,
firstname: null,
lastname: null
}
},
defaultItem: {
id: -1,
username: null,
boss: {
name: null,
firstname: null,
lastname: null
}
},
editedIndex: -1
}
},
methods: {
...mapActions({
getAllWorkgroups: 'wm/getAllWorkgroups',
setWorkgroup: 'wm/setWorkgroup',
updateWorkgroup: 'wm/updateWorkgroup',
getAllUsers: 'usermanager/getUsers',
deleteWorkgroup: 'wm/deleteWorkgroup'
}),
editItem(item) {
this.editedIndex = item.id
this.editedItem = Object.assign({}, item)
this.dialog = true
},
save() {
this.editedItem.id === -1
? this.setWorkgroup(this.editedItem)
: this.updateWorkgroup(this.editedItem)
this.editedItem = Object.assign({}, this.defaultItem)
this.close()
},
close() {
this.dialog = false
setTimeout(() => {
this.editedItem = Object.assign({}, this.defaultItem)
this.editedIndex = -1
}, 300)
},
deleteItem(item) {
confirm(
'Bist du sicher, dass du diese Arbeitsgruppe entfernen willst?'
) && this.deleteWorkgroup(item)
}
},
computed: {
...mapGetters({
workgroupLoading: 'wm/workgroupLoading',
workgroups: 'wm/workgroups',
users: 'usermanager/users',
usersLoading: 'usermanager/usersLoading'
}),
formTitle() {
return this.editedIndex === -1
? 'Neue Arbeitsgruppe'
: 'Bearbeite Arbeitsgruppe'
}
},
created() {
this.getAllWorkgroups()
this.getAllUsers()
console.log(this.users)
}
}
</script>
<style scoped></style>

1
src/css/app.scss Normal file
View File

@ -0,0 +1 @@
// app global css in SCSS form

View File

@ -0,0 +1,24 @@
// Quasar SCSS (& Sass) Variables
// --------------------------------------------------
// To customize the look and feel of this app, you can override
// the Sass/SCSS variables found in Quasar's source Sass/SCSS files.
// Check documentation for full list of Quasar variables
// Your own variables (that are declared here) and Quasar's own
// ones will be available out of the box in your .vue/.scss/.sass files
// It's highly recommended to change the default colors
// to match your app's branding.
// Tip: Use the "Theme Builder" on Quasar's documentation website.
$primary : #1976D2;
$secondary : #26A69A;
$accent : #9C27B0;
$dark : #1D1D1D;
$positive : #21BA45;
$negative : #C10015;
$info : #31CCEC;
$warning : #F2C037;

7
src/env.d.ts vendored Normal file
View File

@ -0,0 +1,7 @@
declare namespace NodeJS {
interface ProcessEnv {
NODE_ENV: string;
VUE_ROUTER_MODE: 'hash' | 'history' | 'abstract' | undefined;
VUE_ROUTER_BASE: string | undefined;
}
}

22
src/index.template.html Normal file
View File

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<head>
<title><%= productName %></title>
<meta charset="utf-8">
<meta name="description" content="<%= productDescription %>">
<meta name="format-detection" content="telephone=no">
<meta name="msapplication-tap-highlight" content="no">
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width<% if (ctx.mode.cordova || ctx.mode.capacitor) { %>, viewport-fit=cover<% } %>">
<link rel="icon" type="image/png" sizes="128x128" href="icons/favicon-128x128.png">
<link rel="icon" type="image/png" sizes="96x96" href="icons/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="32x32" href="icons/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="icons/favicon-16x16.png">
<link rel="icon" type="image/ico" href="favicon.ico">
</head>
<body>
<!-- DO NOT touch the following DIV -->
<div id="q-app"></div>
</body>
</html>

109
src/layouts/MainLayout.vue Normal file
View File

@ -0,0 +1,109 @@
<template>
<q-layout view="lHh Lpr lFf">
<q-header elevated>
<q-toolbar>
<q-btn
flat
dense
round
icon="menu"
aria-label="Menu"
@click="leftDrawerOpen = !leftDrawerOpen"
/>
<q-toolbar-title>
Quasar App
</q-toolbar-title>
<div>Quasar v{{ $q.version }}</div>
</q-toolbar>
</q-header>
<q-drawer
v-model="leftDrawerOpen"
show-if-above
bordered
content-class="bg-grey-1"
>
<q-list>
<q-item-label
header
class="text-grey-8"
>
Essential Links
</q-item-label>
<EssentialLink
v-for="link in essentialLinks"
:key="link.title"
v-bind="link"
/>
</q-list>
</q-drawer>
<q-page-container>
<router-view />
</q-page-container>
</q-layout>
</template>
<script lang="ts">
import EssentialLink from 'components/EssentialLink.vue'
const linksData = [
{
title: 'Docs',
caption: 'quasar.dev',
icon: 'school',
link: 'https://quasar.dev'
},
{
title: 'Github',
caption: 'github.com/quasarframework',
icon: 'code',
link: 'https://github.com/quasarframework'
},
{
title: 'Discord Chat Channel',
caption: 'chat.quasar.dev',
icon: 'chat',
link: 'https://chat.quasar.dev'
},
{
title: 'Forum',
caption: 'forum.quasar.dev',
icon: 'record_voice_over',
link: 'https://forum.quasar.dev'
},
{
title: 'Twitter',
caption: '@quasarframework',
icon: 'rss_feed',
link: 'https://twitter.quasar.dev'
},
{
title: 'Facebook',
caption: '@QuasarFramework',
icon: 'public',
link: 'https://facebook.quasar.dev'
},
{
title: 'Quasar Awesome',
caption: 'Community Quasar projects',
icon: 'favorite',
link: 'https://awesome.quasar.dev'
}
];
import { defineComponent, ref } from '@vue/composition-api';
export default defineComponent({
name: 'MainLayout',
components: { EssentialLink },
setup() {
const leftDrawerOpen = ref(false);
const essentialLinks = ref(linksData);
return {leftDrawerOpen, essentialLinks}
}
});
</script>

View File

@ -1,14 +0,0 @@
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import vuetify from './plugins/vuetify'
Vue.config.productionTip = false
new Vue({
router,
store,
vuetify,
render: h => h(App)
}).$mount('#app')

31
src/pages/Error404.vue Normal file
View File

@ -0,0 +1,31 @@
<template>
<div class="fullscreen bg-blue text-white text-center q-pa-md flex flex-center">
<div>
<div style="font-size: 30vh">
404
</div>
<div class="text-h2" style="opacity:.4">
Oops. Nothing here...
</div>
<q-btn
class="q-mt-xl"
color="white"
text-color="blue"
unelevated
to="/"
label="Go Home"
no-caps
/>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from '@vue/composition-api';
export default defineComponent({
name: 'Error404',
});
</script>

49
src/pages/Index.vue Normal file
View File

@ -0,0 +1,49 @@
<template>
<q-page class="row items-center justify-evenly">
<example-component
title="Example component"
active
:todos="todos"
:meta="meta"
></example-component>
</q-page>
</template>
<script lang="ts">
import { Todo, Meta } from 'components/models';
import ExampleComponent from 'components/CompositionComponent.vue';
import { defineComponent, ref } from '@vue/composition-api';
export default defineComponent({
name: 'PageIndex',
components: { ExampleComponent },
setup() {
const todos = ref<Todo[]>([
{
id: 1,
content: 'ct1'
},
{
id: 2,
content: 'ct2'
},
{
id: 3,
content: 'ct3'
},
{
id: 4,
content: 'ct4'
},
{
id: 5,
content: 'ct5'
}
]);
const meta = ref<Meta>({
totalCount: 1200
});
return { todos, meta };
}
});
</script>

View File

@ -1,103 +0,0 @@
//const main = 'https://192.168.5.128:5000/'
const main = 'http://localhost:5000/'
//const main = 'http://192.168.5.118:5000/'
//const main = 'https://groeger-clan.duckdns.org:5000/'
//const main = 'https://flaschengeist.wu5.de:5000/'
const url = {
login: main + 'login',
logout: main + 'logout',
getUsers: main + 'getUsers',
pricelist: main + 'pricelist',
getTypes: main + 'drinkTypes',
getFinanzerMain: main + 'getFinanzerMain',
bar: main + 'bar',
barGetUser: main + 'barGetUser',
barAddAmount: main + 'baradd',
finanzerAddAmount: main + 'finanzerAddAmount',
finanzerAddCredit: main + 'finanzerAddCredit',
searchUser: main + 'search',
getAllUser: main + 'barGetUsers',
lockUser: main + 'finanzerLock',
finanzerSetConfig: main + 'finanzerSetConfig',
finanzerAddUser: main + 'finanzerAddUser',
finanzerSendAllMail: main + 'finanzerSendAllMail',
finanzerSendOneMail: main + 'finanzerSendOneMail',
userMain: main + 'user/main',
userAddAmount: main + 'user/addAmount',
saveLifetime: main + 'setLifetime',
getLifetime: main + 'getLifetime',
resetPassword: main + 'passwordReset',
freeDrinkListConfig: main + 'freeDrinkListConfig',
freeDrinkListHistory: main + 'freeDrinkListHistory',
freeDrinkListHistoryFromTo: main + 'freeDrinkListHistoryFromTo',
deleteDrinkListHistory: main + 'deleteDrinkListHistory',
freeDrinkListReasons: main + 'freeDrinkListReasons',
deleteFreeDrinkListReason: main + 'deleteFreeDrinkListReason',
freeDrinkTypes: main + 'freeDrinkTypes',
deleteFreeDrinkListConfig: main + 'deleteFreeDrinkListConfig',
vorstand: {
sm: {
addUser: main + 'sm/addUser',
deleteUser: main + 'sm/deleteUser',
getUser: main + 'sm/getUser',
getUsers: main + 'user/jobs',
lockDay: main + 'sm/lockDay',
searchUser: main + 'sm/searchWithExtern',
jobkind: main + 'sm/JobKind',
getAllJobKindsbKinds: main + 'sm/getAllJobKinds',
getJobKind: main + 'sm/getJobKind',
deleteJobKind: main + 'sm/deleteJobKind',
updateJobKindDates: main + 'jk/JobKindDate',
getJobKindDates: main + 'jk/JobKindDate'
},
um: {
setStatus: main + 'um/setStatus',
updateStatus: main + 'um/updateStatus',
deleteStatus: main + 'um/deleteStatus',
updateStatusUser: main + 'um/updateStatusUser',
updateVoting: main + 'um/updateVoting',
updateWorkgroups: main + 'um/updateWorkgroups'
},
wm: {
workgroup: main + 'wgm/workgroup',
getWorkgroup: main + 'wgm/getWorkgroup',
getAllWorkgroups: main + 'wgm/getAllWorkgroups',
deleteWorkgroup: main + 'wgm/deleteWorkgroup'
}
},
user: {
config: main + 'user/saveConfig',
job: main + 'user/job',
jobs: main + 'user/jobs',
addJob: main + 'user/addJob',
getJobOnDates: main + 'user/jobsOnDates',
deleteJob: main + 'user/deleteJob',
getJobInvites: main + 'user/getJobInvites',
setJobInvites: main + 'user/JobInvites',
deletJobInvite: main + 'user/deleteJobInvite',
getJobRequests: main + 'user/getJobRequests',
setJobRequests: main + 'user/JobRequests',
deletJobRequest: main + 'user/deleteJobRequest',
storno: main + 'user/storno',
getAllStatus: main + 'getAllStatus',
getStatus: main + 'getStatus',
valid: main + 'valid',
getAccessTokens: main + 'user/getAccessTokens'
},
barU: {
storno: main + 'bar/storno',
lock: main + 'bar/lock',
addUser: main + 'bar/addUser'
},
gastro: {
setDrink: main + 'gastro/setDrink',
updateDrink: main + 'gastro/updateDrink',
deleteDrink: main + 'gastro/deleteDrink',
setType: main + 'gastro/setDrinkType',
updateType: main + 'gastro/updateDrinkType',
deleteType: main + 'gastro/deleteDrinkType'
}
}
export default url

View File

@ -1,10 +0,0 @@
import Vue from 'vue'
import Vuetify from 'vuetify/lib'
Vue.use(Vuetify)
export default new Vuetify({
icons: {
iconfont: 'mdiSvg'
}
})

View File

@ -1,274 +0,0 @@
import Vue from 'vue'
import VueRouter from 'vue-router'
import FinanzerView from '@/views/FinanzerView'
import Login from '@/views/Login'
import store from '@/store/index'
import GeruechteView from '../views/contents/GeruechteView'
import BarFreedrinksView from '../views/contents/BarFreedrinksView'
import AddAmount from '../components/user/AddAmount'
import CreditOverview from '../components/user/CreditOverview'
import MainView from '../views/MainView'
import UserView from '../views/UserView'
import BarView from '../views/BarView'
import UserNavigation from '../components/user/UserNavigation'
import BarNavigation from '../components/baruser/BarNavigation'
import FinanzerNavigation from '../components/finanzer/FinanzerNavigation'
import Overview from '../components/finanzer/Overview'
import User from '../components/finanzer/User'
import ServiceManagement from '../components/vorstand/ServiceManagement'
import Config from '@/components/user/Config'
import FreedrinkUserView from '@/components/user/freedrink/freedrinkUserView'
import Jobs from '@/components/user/Jobs'
import PriceList from '@/components/pricelist/PriceList'
import ManagementNavigation from '@/components/vorstand/ManagementNavigation'
import GastroNavigation from '@/components/gastro/GastroNavigation'
import PriceListView from '@/views/contents/PriceListView'
import UserManager from '@/components/vorstand/UserManager'
import WorkgroupManagement from '@/components/vorstand/WorkgroupManagement'
import JobKindManager from '@/components/vorstand/JobKindManager'
import JobsRequest from '@/components/user/JobsRequest'
import ResetPassword from '@/components/ResetPassword'
import FreeDrinkList from '@/components/vorstand/FreeDrinkList'
import FreeDrinkListMain from '@/components/vorstand/FreeDrinkList/FreeDrinkListMain'
import FreeDrinkListConfig from '@/components/vorstand/FreeDrinkList/FreeDrinkListConfig'
import FreeDrinkListJob from '@/components/vorstand/FreeDrinkList/FreeDrinkListJob'
import FreeDrinkListWorkgroup from '@/components/vorstand/FreeDrinkList/FreeDrinkListWorkgroup'
import FreeDrinkListBand from '@/components/vorstand/FreeDrinkList/FreeDrinkListBand'
Vue.use(VueRouter)
const rootPath = ''
const routes = [
{
path: rootPath + '/resetPassword',
name: 'resetPassword',
component: ResetPassword
},
{
path: rootPath + '/cookies',
name: 'cookies'
},
{
path: rootPath + '/pricelist',
name: 'priceListNoLogin',
component: PriceListView
},
{
path: rootPath + '/login',
name: 'login',
component: Login
},
{
path: rootPath + '/main',
name: 'main',
component: MainView,
children: [
{
path: 'management',
name: 'management',
components: { nav: ManagementNavigation, default: BarView },
children: [
{
name: 'serviceManagement',
path: 'servicemanagement/:year/:month',
component: ServiceManagement
},
{
path: 'usermanager',
name: 'userManager',
component: UserManager
},
{
path: 'workgroupmanagement',
name: 'workgroupManagement',
component: WorkgroupManagement
},
{
path: 'jobkindmanagement',
name: 'jobkindManagement',
component: JobKindManager
},
{
path: 'freeDrinkList',
name: 'freeDrinkList',
component: FreeDrinkList,
children: [
{
path: 'main',
name: 'freeDrinkListMain',
component: FreeDrinkListMain
},
{
path: 'config',
name: 'freeDrinkListConfig',
component: FreeDrinkListConfig
},
{
path: 'freeDrinkListJob',
name: 'freeDrinkListJob',
component: FreeDrinkListJob
},
{
path: 'freeDrinkListWorkgroup',
name: 'freeDrinkListWorkgroup',
component: FreeDrinkListWorkgroup
},
{
path: 'freeDrinkListBand',
name: 'freeDrinkListBand',
component: FreeDrinkListBand
}
]
}
]
},
{
path: 'user',
name: 'user',
components: { nav: UserNavigation, default: UserView },
children: [
{
path: 'add',
name: 'add',
component: AddAmount
},
{
path: 'freedrinkUser',
name: 'freedrinkUser',
component: FreedrinkUserView
},
{
path: 'overview',
name: 'userOverview',
component: CreditOverview
},
{
path: 'config',
name: 'userConfig',
component: Config
},
{
path: 'jobs/:year/:month',
name: 'userJobs',
component: Jobs
},
{
path: 'jobRequests/:kind',
name: 'jobRequests',
component: JobsRequest
}
]
},
{
path: 'bar',
name: 'bar',
components: { nav: BarNavigation, default: BarView },
children: [
{
path: 'geruecht',
name: 'geruecht',
component: GeruechteView
},
{
path: 'baruserFreedrinks',
name: 'baruserFreedrinks',
component: BarFreedrinksView
}
]
},
{
path: 'finanzer',
name: 'finanzer',
components: { default: FinanzerView, nav: FinanzerNavigation },
children: [
{
path: 'overview',
name: 'overview',
component: Overview
},
{
path: 'user/:id',
name: 'activeUser',
props: true,
component: User
}
]
},
{
path: 'gastro',
name: 'gastro',
components: { nav: GastroNavigation, default: BarView },
children: [
{
path: 'pricelist',
name: 'gastroPricelist',
component: PriceList
}
]
}
]
},
{
path: '*',
redirect: {
name: 'login'
}
}
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
router.beforeEach((to, from, next) => {
store.dispatch('fetchAccessToken')
if (to.name === 'main') {
if (
to.name === 'finanzer' ||
to.name === 'overview' ||
to.name === 'activeUser'
) {
if (!store.state.login.user.group.includes('moneymaster')) {
next({ name: 'login' })
}
}
if (to.name === 'bar' || to.name === 'geruecht') {
if (!store.state.login.user.group.includes('bar')) {
next({ name: 'login' })
}
}
if (
to.name === 'user' ||
to.name === 'userOverview' ||
to.name === 'userConfig' ||
to.name === 'userJobs' ||
to.name === 'jobRequests'
) {
if (!store.state.login.user.group.includes('user')) {
next({ name: 'login' })
}
}
if (!store.state.login.user.accessToken) {
next({ name: 'login' })
}
}
if (to.name === 'login') {
if (store.state.login.user.accessToken) {
if (store.state.login.user.group.includes('moneymaster')) {
next({ name: 'overview' })
} else if (store.state.login.user.group.includes('bar')) {
next({ name: 'geruecht' })
} else if (store.state.login.user.group.includes('user')) {
next({ name: 'add' })
} else if (store.state.login.user.group.includes('extern')) {
next({ name: 'main' })
}
}
}
next()
})
export default router

27
src/router/index.ts Normal file
View File

@ -0,0 +1,27 @@
import { route } from 'quasar/wrappers';
import VueRouter from 'vue-router';
import { Store } from 'vuex';
import { StateInterface } from '../store';
import routes from './routes';
/*
* If not building with SSR mode, you can
* directly export the Router instantiation
*/
export default route<Store<StateInterface>>(function ({ Vue }) {
Vue.use(VueRouter);
const Router = new VueRouter({
scrollBehavior: () => ({ x: 0, y: 0 }),
routes,
// Leave these as is and change from quasar.conf.js instead!
// quasar.conf.js -> build -> vueRouterMode
// quasar.conf.js -> build -> publicPath
mode: process.env.VUE_ROUTER_MODE,
base: process.env.VUE_ROUTER_BASE
});
return Router;
})

20
src/router/routes.ts Normal file
View File

@ -0,0 +1,20 @@
import { RouteConfig } from 'vue-router';
const routes: RouteConfig[] = [
{
path: '/',
component: () => import('layouts/MainLayout.vue'),
children: [
{ path: '', component: () => import('pages/Index.vue') }
]
},
// Always leave this as last one,
// but you can also remove it
{
path: '*',
component: () => import('pages/Error404.vue')
}
];
export default routes;

5
src/shims-vue.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
// Mocks all files ending in `.vue` showing them as plain Vue instances
declare module '*.vue' {
import Vue from 'vue';
export default Vue;
}

View File

@ -1,37 +0,0 @@
import Vue from 'vue'
import Vuex from 'vuex'
import login from './modules/login'
import finanzerUsers from './modules/finanzerUsers'
import barUsers from '@/store/modules/barUsers'
import user from '@/store/modules/user'
import sm from '@/store/modules/serviceManagement'
import jobs from '@/store/modules/jobs'
import priceList from '@/store/modules/pricelist'
import usermanager from '@/store/modules/userManager'
import wm from '@/store/modules/workgroupManagement'
import jkm from '@/store/modules/jobkindManager'
import jobInvites from '@/store/modules/jobInvites'
import jobRequests from '@/store/modules/jobRequests'
import connectionError from '@/store/modules/connectionError'
import freeDrinkList from '@/store/modules/freeDrinkList'
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
login,
finanzerUsers,
barUsers,
user,
sm,
jobs,
priceList,
usermanager,
wm,
jkm,
jobInvites,
jobRequests,
connectionError,
freeDrinkList
}
})

33
src/store/index.ts Normal file
View File

@ -0,0 +1,33 @@
import { store } from 'quasar/wrappers';
import Vuex from 'vuex';
// import example from './module-example';
// import { ExampleStateInterface } from './module-example/state';
/*
* If not building with SSR mode, you can
* directly export the Store instantiation
*/
export interface StateInterface {
// Define your own store structure, using submodules if needed
// example: ExampleStateInterface;
// Declared as unknown to avoid linting issue. Best to strongly type as per the line above.
example: unknown;
}
export default store(function ({ Vue }) {
Vue.use(Vuex);
const Store = new Vuex.Store<StateInterface>({
modules: {
// example
},
// enable strict mode (adds overhead!)
// for dev mode only
strict: !!process.env.DEV
});
return Store;
});

View File

@ -0,0 +1,11 @@
import { ActionTree } from 'vuex';
import { StateInterface } from '../index';
import { ExampleStateInterface } from './state';
const actions: ActionTree<ExampleStateInterface, StateInterface> = {
someAction (/* context */) {
// your code
}
};
export default actions;

View File

@ -0,0 +1,11 @@
import { GetterTree } from 'vuex';
import { StateInterface } from '../index';
import { ExampleStateInterface } from './state';
const getters: GetterTree<ExampleStateInterface, StateInterface> = {
someAction (/* context */) {
// your code
}
};
export default getters;

View File

@ -0,0 +1,16 @@
import { Module } from 'vuex';
import { StateInterface } from '../index';
import state, { ExampleStateInterface } from './state';
import actions from './actions';
import getters from './getters';
import mutations from './mutations';
const exampleModule: Module<ExampleStateInterface, StateInterface> = {
namespaced: true,
actions,
getters,
mutations,
state
};
export default exampleModule;

View File

@ -0,0 +1,10 @@
import { MutationTree } from 'vuex';
import { ExampleStateInterface } from './state';
const mutation: MutationTree<ExampleStateInterface> = {
someMutation (/* state: ExampleStateInterface */) {
// your code
}
};
export default mutation;

View File

@ -0,0 +1,9 @@
export interface ExampleStateInterface {
prop: boolean;
}
const state: ExampleStateInterface = {
prop: false
};
export default state;

View File

@ -1,411 +0,0 @@
import axios from 'axios'
import url from '@/plugins/routes'
const timeout = 5000
const state = {
users: [],
allUsers: [],
filter: '',
usersLoading: false,
allUsersLoading: false,
messages: [],
menu: false,
locked: false
}
const mutations = {
setAllUsers: (state, users) => {
state.allUsers = []
state.allUsers = users
for (let i = 0; i < state.allUsers.length; i++) {
state.allUsers[i].fullName =
state.allUsers[i].firstname + ' ' + state.allUsers[i].lastname
}
},
setUsers: (state, users) => {
for (let user in users) {
let existuser = state.users.find(a => {
return user === a.username
})
if (users[user].last_seen != null || users[user].last_seen != undefined) {
users[user].last_seen = new Date(
users[user].last_seen.year,
users[user].last_seen.month - 1,
users[user].last_seen.day,
users[user].last_seen.hour,
users[user].last_seen.minute,
users[user].last_seen.second
)
}
if (existuser) {
existuser.sername = users[user].username
existuser.firstname = users[user].firstname
existuser.lastname = users[user].lastname
existuser.locked = users[user].locked
existuser.amount = users[user].amount
existuser.type = users[user].type
existuser.limit = users[user].limit
existuser.last_seen = users[user].last_seen
existuser.autoLock = users[user].autoLock
? users[user].autoLock
: existuser.autoLock
} else {
state.users.push({
username: users[user].username,
firstname: users[user].firstname,
lastname: users[user].lastname,
locked: users[user].locked,
amount: users[user].amount,
type: users[user].type,
loading: false,
limit: users[user].limit,
last_seen: users[user].last_seen,
autoLock: users[user].autoLock
})
}
}
mutations.sortUsers(state)
},
updateUser: (state, data) => {
let index = state.users.indexOf(
state.users.find(a => {
return a.username === data.username
})
)
if (data.loading !== undefined) state.users[index].loading = data.loading
if (data.last_seen !== undefined)
state.users[index].last_seen = data.last_seen
},
sortUsers: state => {
state.users = state.users.sort((a, b) => {
if (a.lastname > b.lastname) return 1
if (a.lastname < b.lastname) return -1
if (a.firstname > b.firstname) return 1
if (a.firstname < b.firstname) return -1
return 0
})
},
setFilter: (state, filter) => {
state.filter = filter
},
setUsersLoading: (state, value) => {
state.usersLoading = value
},
setAllUsersLoading: (state, value) => {
state.allUsersLoading = value
},
addMessage: (state, data) => {
var message = null
if (state.messages.length > 0) {
if (
state.messages[0].user.username === data.user.username &&
!data.error
) {
message = state.messages[0]
if ((new Date() - state.messages[0].date) / 1000 < 2) {
clearTimeout(message.timeout)
message.amount = message.amount + data.amount
message.visible = true
message.date = new Date()
message.timeout = setTimeout(() => {
if (!message.error) {
message.visible = false
}
}, 5000)
return
} else {
message.visible = false
}
}
}
let message2 = {
user: data.user,
error: data.error,
storno: false,
loading: false,
visible: true,
amount: data.amount,
date: new Date(),
timeout: setTimeout(() => {
if (!message2.error) {
message2.visible = false
}
}, 5000)
}
state.messages.unshift(message2)
},
updateMessage: (state, data) => {
var message = state.messages.find(msg => {
return msg.date - data.date === 0 ? true : false
})
if (message) {
if (data.storno !== undefined) message.storno = data.storno
if (data.loading !== undefined) message.loading = data.loading
}
},
setMenu: (state, value) => {
state.menu = value
},
setLocked: (satet, value) => {
state.locked = value
}
}
const actions = {
// eslint-disable-next-line no-unused-vars
async getUsers({ commit, rootState, dispatch }) {
commit('setUsersLoading', true)
try {
const response = await axios.get(url.bar, {
headers: { Token: rootState.login.user.accessToken },
timeout
})
commit('setUsers', response.data)
dispatch('getLifetime', null, { root: true })
} catch (e) {
if (e.message == 'Network Error') {
dispatch('connectionError/addError', null, { root: true })
}
if (e.response)
if (e.response.status === 401) {
dispatch('getLifetime', null, { root: true })
location.reload()
}
}
commit('setUsersLoading', false)
},
async addAmount({ commit, rootState, dispatch }, data) {
try {
commit('updateUser', { username: data.username, loading: true })
} catch (e) {
//error
}
try {
const response = await axios.post(
url.barAddAmount,
{ userId: data.username, amount: data.amount },
{ headers: { Token: rootState.login.user.accessToken }, timeout }
)
commit('setUsers', { [response.data.username]: response.data })
commit('addMessage', {
user: data.user,
amount: data.amount,
error: false
})
dispatch('getLifetime', null, { root: true })
} catch (e) {
console.log(typeof e)
for (const [key, value] of Object.entries(e)) {
console.log(`${key}: ${value}`)
}
if (e.message == 'Network Error') {
dispatch('connectionError/addError', null, { root: true })
}
commit('addMessage', {
user: data.user,
amount: data.amount,
error: true
})
if (e.response)
if (e.response.status === 401) {
dispatch('getLifetime', null, { root: true })
location.reload()
}
}
try {
commit('updateUser', { username: data.username, loading: false })
} catch (e) {
//error
}
},
async addCreditList({ commit, rootState, dispatch }, data) {
try {
commit('updateUser', { username: data.username, loading: true })
} catch (e) {
//error
}
try {
const response = await axios.post(
url.barGetUser,
{ userId: data.username },
{ headers: { Token: rootState.login.user.accessToken }, timeout }
)
commit('setUsers', { [response.data.username]: response.data })
dispatch('getLifetime', null, { root: true })
} catch (e) {
if (e.message == 'Network Error') {
dispatch('connectionError/addError', null, { root: true })
}
if (e.response)
if (e.response.status === 401) {
dispatch('getLifetime', null, { root: true })
location.reload()
}
}
try {
commit('updateUser', {
username: data.username,
loading: false,
last_seen: new Date()
})
} catch {
// error
}
},
async getAllUsers({ commit, rootState, dispatch }) {
commit('setAllUsersLoading', true)
try {
const response = await axios.get(url.searchUser, {
headers: { Token: rootState.login.user.accessToken },
timeout
})
commit('setAllUsers', response.data)
dispatch('getLifetime', null, { root: true })
} catch (e) {
if (e.message == 'Network Error') {
dispatch('connectionError/addError', null, { root: true })
}
if (e.response)
if (e.response.data === 401) {
dispatch('getLifetime', null, { root: true })
location.reload()
}
}
commit('setAllUsersLoading', false)
},
async storno({ commit, rootState, dispatch }, data) {
commit('updateMessage', { date: data.date, loading: true })
try {
const response = await axios.post(
url.barU.storno,
{
userId: data.username,
amount: data.amount
},
{ headers: { Token: rootState.login.user.accessToken }, timeout }
)
commit('setUsers', { [response.data.username]: response.data })
commit('updateMessage', { date: data.date, storno: true })
dispatch('getLifetime', null, { root: true })
} catch (e) {
if (e.message == 'Network Error') {
dispatch('connectionError/addError', null, { root: true })
}
if (e.response)
if (e.response.status === 401) {
dispatch('getLifetime', null, { root: true })
location.reload()
}
}
commit('updateMessage', { date: data.date, loading: false })
},
async getLocked({ commit, rootState, dispatch }) {
try {
const response = await axios.get(url.barU.lock, {
headers: { Token: rootState.login.user.accessToken },
timeout
})
commit('setLocked', response.data.value)
} catch (e) {
if (e.message == 'Network Error') {
dispatch('connectionError/addError', null, { root: true })
}
if (e.response)
if (e.response.status === 401) {
dispatch('getLifetime', null, { root: true })
location.reload()
}
}
},
async setLocked({ commit, rootState, dispatch }) {
try {
const response = await axios.post(
url.barU.lock,
{ value: true },
{ headers: { Token: rootState.login.user.accessToken }, timeout }
)
commit('setLocked', response.data.value)
} catch (e) {
if (e.message == 'Network Error') {
dispatch('connectionError/addError', null, { root: true })
}
if (e.response)
if (e.response.status === 401) {
dispatch('getLifetime', null, { root: true })
location.reload()
}
}
},
async unlock({ commit, rootState, dispatch }, password) {
try {
const valid = await axios.post(
url.user.valid,
{ password: password },
{ headers: { Token: rootState.login.user.accessToken }, timeout }
)
if (valid.data.ok === 'ok') {
const response = await axios.post(
url.barU.lock,
{ value: false },
{ headers: { Token: rootState.login.user.accessToken } }
)
commit('setLocked', response.data.value)
}
} catch (e) {
if (e.message == 'Network Error') {
dispatch('connectionError/addError', null, { root: true })
}
if (e.response)
if (e.response.status === 401) {
dispatch('getLifetime', null, { root: true })
location.reload()
}
}
},
setLockStatus({ commit }, status) {
commit('setLocked', status)
},
setFilter({ commit }, data) {
commit('setFilter', data)
},
activateMenu({ commit }) {
commit('setMenu', true)
},
deactivateMenu({ commit }) {
commit('setMenu', false)
}
}
const getters = {
users: state => {
return state.users
},
allUsers: state => {
return state.allUsers
},
filter: state => {
return state.filter
},
usersLoading: state => {
return state.usersLoading
},
allUsersLoading: state => {
return state.allUsersLoading
},
messages: state => {
return state.messages
},
menu: state => {
return state.menu
},
locked: state => {
return state.locked
}
}
export default {
namespaced: true,
state,
mutations,
actions,
getters
}

View File

@ -1,43 +0,0 @@
const state = {
errors: []
}
const mutations = {
addError: state => {
state.errors.push({
message: 'Connection Error: Server nicht verfügbar!',
visible: true
})
},
deleteErrors: state => {
state.errors = []
}
}
const actions = {
addError: ({ commit }) => {
commit('addError')
},
deleteErrors: ({ commit }) => {
commit('deleteErrors')
}
}
const getters = {
errors: state => {
return state.errors
},
visible: state => {
return state.errors.find(error => {
return error.visible
})
}
}
export default {
namespaced: true,
state,
mutations,
actions,
getters
}

Some files were not shown because too many files have changed in this diff Show More