Browse Source

Client initialisiert

tags/v0.9.1
akimmig 4 years ago
parent
commit
45a3040f44
100 changed files with 0 additions and 66405 deletions
  1. +0
    -3
      client.ts/.browserslistrc
  2. +0
    -5
      client.ts/.editorconfig
  3. +0
    -20
      client.ts/.eslintrc.js
  4. +0
    -23
      client.ts/.gitignore
  5. +0
    -24
      client.ts/README.md
  6. +0
    -5
      client.ts/babel.config.js
  7. +0
    -32809
      client.ts/package-lock.json
  8. +0
    -55
      client.ts/package.json
  9. BIN
      client.ts/public/favicon.ico
  10. +0
    -19
      client.ts/public/index.html
  11. +0
    -130
      client.ts/src/App.vue
  12. BIN
      client.ts/src/assets/logo.png
  13. +0
    -1
      client.ts/src/assets/logo.svg
  14. +0
    -107
      client.ts/src/components/Confirm.vue
  15. +0
    -90
      client.ts/src/components/DateSelector.vue
  16. +0
    -139
      client.ts/src/components/DateTimeSelector.vue
  17. +0
    -20
      client.ts/src/components/Demo.vue
  18. +0
    -9
      client.ts/src/components/base/Card.vue
  19. +0
    -77
      client.ts/src/components/base/EditDialog.vue
  20. +0
    -76
      client.ts/src/components/base/EditDialogDate.vue
  21. +0
    -82
      client.ts/src/components/base/EditDialogSelect.vue
  22. +0
    -69
      client.ts/src/components/base/Item.vue
  23. +0
    -142
      client.ts/src/components/base/ItemGroup.vue
  24. +0
    -24
      client.ts/src/components/base/ItemSubGroup.vue
  25. +0
    -60
      client.ts/src/components/base/MaterialAlert.vue
  26. +0
    -176
      client.ts/src/components/base/MaterialCard.vue
  27. +0
    -95
      client.ts/src/components/base/MaterialChartCard.vue
  28. +0
    -164
      client.ts/src/components/base/MaterialDialog.vue
  29. +0
    -70
      client.ts/src/components/base/MaterialDropdown.vue
  30. +0
    -66
      client.ts/src/components/base/MaterialSnackbar.vue
  31. +0
    -113
      client.ts/src/components/base/MaterialStatsCard.vue
  32. +0
    -43
      client.ts/src/components/base/MaterialTabs.vue
  33. +0
    -76
      client.ts/src/components/base/MaterialTestimony.vue
  34. +0
    -110
      client.ts/src/components/base/MaterialWizard.vue
  35. +0
    -455
      client.ts/src/components/base/NumberInput.vue
  36. +0
    -34
      client.ts/src/components/base/Subheading.vue
  37. +0
    -40
      client.ts/src/components/base/VComponent.vue
  38. +0
    -17
      client.ts/src/components/e403.vue
  39. +0
    -27
      client.ts/src/main.ts
  40. +0
    -19
      client.ts/src/plugins/base.ts
  41. +0
    -4
      client.ts/src/plugins/compositionAPI.ts
  42. +0
    -4
      client.ts/src/plugins/cookies.ts
  43. +0
    -82
      client.ts/src/plugins/graphql.ts
  44. +0
    -20
      client.ts/src/plugins/router.ts
  45. +0
    -38
      client.ts/src/plugins/store.ts
  46. +0
    -31
      client.ts/src/plugins/vuetify.ts
  47. +0
    -55
      client.ts/src/sass/overrides.sass
  48. +0
    -34
      client.ts/src/sass/variables.scss
  49. +0
    -54
      client.ts/src/sass/vuetify-material/_appbar.sass
  50. +0
    -1
      client.ts/src/sass/vuetify-material/_badge.sass
  51. +0
    -67
      client.ts/src/sass/vuetify-material/_buttons.sass
  52. +0
    -30
      client.ts/src/sass/vuetify-material/_calendar.sass
  53. +0
    -107
      client.ts/src/sass/vuetify-material/_card.sass
  54. +0
    -9
      client.ts/src/sass/vuetify-material/_chip.sass
  55. +0
    -29
      client.ts/src/sass/vuetify-material/_footer.sass
  56. +0
    -45
      client.ts/src/sass/vuetify-material/_form.sass
  57. +0
    -3
      client.ts/src/sass/vuetify-material/_map.sass
  58. +0
    -25
      client.ts/src/sass/vuetify-material/_modal.sass
  59. +0
    -12
      client.ts/src/sass/vuetify-material/_notification.sass
  60. +0
    -28
      client.ts/src/sass/vuetify-material/_pages.sass
  61. +0
    -5
      client.ts/src/sass/vuetify-material/_pagination.sass
  62. +0
    -23
      client.ts/src/sass/vuetify-material/_rtl.sass
  63. +0
    -36
      client.ts/src/sass/vuetify-material/_settings.sass
  64. +0
    -79
      client.ts/src/sass/vuetify-material/_sidebar.sass
  65. +0
    -12
      client.ts/src/sass/vuetify-material/_tab.sass
  66. +0
    -22
      client.ts/src/sass/vuetify-material/_table.sass
  67. +0
    -16
      client.ts/src/sass/vuetify-material/_timeline.sass
  68. +0
    -4
      client.ts/src/sass/vuetify-material/_vectormap.sass
  69. +0
    -23
      client.ts/src/sass/vuetify-material/_view.sass
  70. +0
    -38
      client.ts/src/views/Index.vue
  71. +0
    -183
      client.ts/src/views/components/core/AppBar.vue
  72. +0
    -151
      client.ts/src/views/components/core/Drawer.vue
  73. +0
    -69
      client.ts/src/views/components/core/Footer.vue
  74. +0
    -77
      client.ts/src/views/components/core/Menu.vue
  75. +0
    -17
      client.ts/src/views/components/core/View.vue
  76. +0
    -40
      client.ts/tsconfig.json
  77. +0
    -5
      client.ts/vue.config.js
  78. +0
    -23
      client.vue3/.gitignore
  79. +0
    -15
      client.vue3/.graphqlconfig
  80. +0
    -24
      client.vue3/README.md
  81. +0
    -5
      client.vue3/babel.config.js
  82. +0
    -28367
      client.vue3/package-lock.json
  83. +0
    -57
      client.vue3/package.json
  84. BIN
      client.vue3/public/favicon.ico
  85. +0
    -17
      client.vue3/public/index.html
  86. +0
    -110
      client.vue3/src/App.vue
  87. BIN
      client.vue3/src/assets/favicon.ico
  88. BIN
      client.vue3/src/assets/logo.png
  89. +0
    -1
      client.vue3/src/assets/logo.svg
  90. +0
    -107
      client.vue3/src/components/Confirm.vue
  91. +0
    -90
      client.vue3/src/components/DateSelector.vue
  92. +0
    -139
      client.vue3/src/components/DateTimeSelector.vue
  93. +0
    -20
      client.vue3/src/components/Demo.vue
  94. +0
    -9
      client.vue3/src/components/base/Card.vue
  95. +0
    -77
      client.vue3/src/components/base/EditDialog.vue
  96. +0
    -76
      client.vue3/src/components/base/EditDialogDate.vue
  97. +0
    -85
      client.vue3/src/components/base/EditDialogSelect.vue
  98. +0
    -69
      client.vue3/src/components/base/Item.vue
  99. +0
    -142
      client.vue3/src/components/base/ItemGroup.vue
  100. +0
    -0
      client.vue3/src/components/base/ItemSubGroup.vue

+ 0
- 3
client.ts/.browserslistrc View File

@@ -1,3 +0,0 @@
> 1%
last 2 versions
not dead

+ 0
- 5
client.ts/.editorconfig View File

@@ -1,5 +0,0 @@
[*.{js,jsx,ts,tsx,vue}]
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
insert_final_newline = true

+ 0
- 20
client.ts/.eslintrc.js View File

@@ -1,20 +0,0 @@
module.exports = {
root: true,
env: {
node: true
},
extends: [
'plugin:vue/essential',
'@vue/standard',
'@vue/typescript/recommended'
],
parserOptions: {
ecmaVersion: 2020
},
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/ban-ts-ignore": "off"
}
}

+ 0
- 23
client.ts/.gitignore View File

@@ -1,23 +0,0 @@
.DS_Store
node_modules
/dist


# local env files
.env.local
.env.*.local

# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*

# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

+ 0
- 24
client.ts/README.md View File

@@ -1,24 +0,0 @@
# client

## Project setup
```
npm install
```

### Compiles and hot-reloads for development
```
npm run serve
```

### Compiles and minifies for production
```
npm run build
```

### Lints and fixes files
```
npm run lint
```

### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).

+ 0
- 5
client.ts/babel.config.js View File

@@ -1,5 +0,0 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}

+ 0
- 32809
client.ts/package-lock.json
File diff suppressed because it is too large
View File


+ 0
- 55
client.ts/package.json View File

@@ -1,55 +0,0 @@
{
"name": "client",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"@fortawesome/fontawesome-pro": "^5.15.3",
"@mdi/font": "^5.9.55",
"@vue/composition-api": "1.0.0-rc.8",
"apollo-cache-inmemory": "^1.6.6",
"apollo-client": "^2.6.10",
"core-js": "^3.6.5",
"js-cookie": "^2.2.1",
"lodash": "^4.17.21",
"subscriptions-transport-ws": "^0.9.18",
"uuid": "^3.4.0",
"vue": "^2.6.11",
"vue-apollo": "^3.0.7",
"vue-class-component": "^7.2.3",
"vue-cookies": "^1.7.4",
"vue-property-decorator": "^9.1.2",
"vue-router": "^3.2.0",
"vuetify": "^2.4.0",
"vuex": "^3.4.0"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^4.18.0",
"@typescript-eslint/parser": "^4.18.0",
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-plugin-router": "~4.5.0",
"@vue/cli-plugin-typescript": "~4.5.0",
"@vue/cli-plugin-vuex": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"@vue/eslint-config-standard": "^5.1.2",
"@vue/eslint-config-typescript": "^7.0.0",
"eslint": "^6.7.2",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.0",
"eslint-plugin-vue": "^6.2.2",
"node-sass": "^4.12.0",
"sass": "^1.32.0",
"sass-loader": "^10.0.0",
"typescript": "~4.1.5",
"vue-cli-plugin-vuetify": "~2.4.0",
"vue-template-compiler": "^2.6.11",
"vuetify-loader": "^1.7.0"
}
}

BIN
client.ts/public/favicon.ico View File

Before After

+ 0
- 19
client.ts/public/index.html 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 %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.2/css/all.css" integrity="sha384-hWVjflwFxL6sNzntih27bfxkr27PmbbK/iSvJ+a4+0owXq79v+lsFkW54bOGbiDQ" crossorigin="anonymous">
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> 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>

+ 0
- 130
client.ts/src/App.vue View File

@@ -1,130 +0,0 @@
<template>
<div>
<router-view />
<div
v-if="!connected"
id="loader"
>
<div>
<v-progress-circular
indeterminate
:size="64"
:width="8"
color="rgb(255, 4, 29)"
/>
<p>Verbinde zu Server...</p>
</div>
</div>
<confirm ref="confirm" />
<v-snackbar
v-model="error_visible"
class="error_snack"
:timeout="5000"
top
>
{{ error_text }}

<template #action="{ attrs }">
<v-btn
dark
text
v-bind="attrs"
@click="error_visible = false"
>
Schließen
</v-btn>
</template>
</v-snackbar>
</div>
</template>

<script lang="ts">
import Vue from 'vue'
import { useGraphQL } from '@/plugins/graphql'
import { mapState } from 'vuex'

export default Vue.extend({
name: 'App',

setup (props, context) {
return {
...useGraphQL(context)
}
},

computed: {
...mapState(['profile']),
error_text () {
return this.$store.state.snackbar.text
},
error_visible: {
get () {
return this.$store.state.snackbar.visible
},
set (v) {
if (!v) this.$store.commit('CLOSE_SNACKBAR')
}
}
},

mounted () {
this.login('a.kimmig@dhg-rw.de', 'Passwort')
}
})
</script>

<style scoped>
#loader {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
width: 100vw;
height: 100vh;
z-index: 9999;
background: rgba(255, 255, 255, 0.8);
text-align: center;
}

#loader > div {
height: 100vh;
width: 100vw;
display: table-cell;
vertical-align: middle;
text-align: center;
}

#loader > div > p {
font-family: "Roboto", sans-serif;
margin-top: 16px;
}
</style>

<style>
.changed {
color: rgb(255, 4, 29);
}

.changefade {
color: inherit;
transition: color 0.8s;
}

.trhover:hover {
cursor: pointer;
}

.center_switch > div > div > div {
margin: auto !important;
}

.error_snack .v-snack__wrapper {
font-family: "Roboto", sans-serif;
background-color: red !important;
}

.row-clickable tbody tr:hover {
cursor: pointer;
}
</style>

BIN
client.ts/src/assets/logo.png View File

Before After
Width: 200  |  Height: 200  |  Size: 6.7KB

+ 0
- 1
client.ts/src/assets/logo.svg 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>

+ 0
- 107
client.ts/src/components/Confirm.vue View File

@@ -1,107 +0,0 @@
<template>
<v-dialog
v-model="dialog"
:max-width="options.width"
:style="{ zIndex: options.zIndex }"
@keydown.esc="cancel"
>
<v-card>
<v-toolbar
dark
:color="options.color"
dense
flat
>
<v-toolbar-title class="white--text">
{{ title }}
</v-toolbar-title>
</v-toolbar>
<v-card-text
v-show="!!message"
class="pa-4"
>
{{ message }}
</v-card-text>
<v-card-actions class="pt-0">
<v-spacer />
<v-btn
color="primary darken-1"
@click.native="agree"
>
Ja
</v-btn>
<v-btn
color="grey"
@click.native="cancel"
>
Abbrechen
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>

<script>
/**
* Vuetify Confirm Dialog component
*
* Insert component where you want to use it:
* <confirm ref="confirm"></confirm>
*
* Call it:
* this.$refs.confirm.open('Delete', 'Are you sure?', { color: 'red' }).then((confirm) => {})
* Or use await:
* if (await this.$refs.confirm.open('Delete', 'Are you sure?', { color: 'red' })) {
* // yes
* }
* else {
* // cancel
* }
*
* Alternatively you can place it in main App component and access it globally via this.$root.$confirm
* <template>
* <v-app>
* ...
* <confirm ref="confirm"></confirm>
* </v-app>
* </template>
*
* mounted() {
* this.$root.$confirm = this.$refs.confirm.open
* }
*/
export default {
data: () => ({
dialog: false,
resolve: null,
reject: null,
message: null,
title: null,
options: {
color: 'primary',
width: 290,
zIndex: 200
}
}),
methods: {
open (title, message, options) {
this.dialog = true
this.title = title
this.message = message
this.options = Object.assign(this.options, options)
return new Promise((resolve, reject) => {
this.resolve = resolve
this.reject = reject
})
},
agree () {
this.resolve(true)
this.dialog = false
},
cancel () {
this.resolve(false)
this.dialog = false
}
}
}
</script>

+ 0
- 90
client.ts/src/components/DateSelector.vue View File

@@ -1,90 +0,0 @@
<template>
<v-menu
v-model="show"
:close-on-content-click="false"
:nudge-right="40"
transition="scale-transition"
offset-y
min-width="290px"
>
<template #activator="{ on }">
<v-text-field
v-model="dateFormatted"
:label="label"
:clearable="clearable"
prepend-icon="mdi-calendar"
readonly
v-on="on"
/>
</template>
<v-date-picker
v-model="date"
:first-day-of-week="1"
@input="show = false"
/>
</v-menu>
</template>

<script>
export default {
name: 'DateSelector',

props: {
value: {
type: String,
default: ''
},
label: {
type: String,
default: ''
},
clearable: {
type: Boolean,
required: false,
default: false
}
},

data: () => ({
show: false
}),

computed: {
dateFormatted: {
get () {
return this.formatDate(this.value)
},
set (val) {
this.$emit('input', this.parseDate(val))
}
},
date: {
get () {
return this.value
},
set (val) {
this.$emit('input', val)
}
}
},

methods: {
formatDate (date) {
if (!date) return null

const [year, month, day] = date.split('-')
return `${day}.${month}.${year}`
},
parseDate (date) {
if (!date) return null

const [day, month, year] = date.split('/')
return `${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')}`
}
}
}
</script>

<style scoped>

</style>

+ 0
- 139
client.ts/src/components/DateTimeSelector.vue View File

@@ -1,139 +0,0 @@
<template>
<v-row>
<v-col cols="6">
<v-menu
v-model="showdate"
:close-on-content-click="false"
:nudge-right="40"
transition="scale-transition"
offset-y
min-width="290px"
>
<template #activator="{ on }">
<v-text-field
:value="dateFormatted"
:label="`${label}-Datum`"
:disabled="disabled"
prepend-icon="far fa-calendar-day"
readonly
v-on="on"
/>
</template>
<v-date-picker
v-model="date"
:first-day-of-week="1"
@input="showdate = false"
/>
</v-menu>
</v-col>
<v-col cols="6">
<v-menu
v-model="showtime"
:close-on-content-click="false"
:nudge-right="40"
transition="scale-transition"
offset-y
min-width="290px"
>
<template #activator="{ on }">
<v-text-field
:value="timeFormatted"
:label="`${label}-Uhrzeit`"
:disabled="disabled"
prepend-icon="far fa-clock"
readonly
v-on="on"
/>
</template>
<v-time-picker
v-model="time"
format="24hr"
:allowed-minutes="m => m % 5 === 0"
@input="showtime = false"
/>
</v-menu>
</v-col>
</v-row>
</template>

<script>
export default {
name: 'DateSelector',

props: {
value: {
type: String,
default: ''
},
label: {
type: String,
default: ''
},
disabled: {
type: Boolean,
default: false
}
},

data: () => ({
showdate: false,
showtime: false
}),

computed: {
_date () {
return (this.value?.split(' ') || [])[0] || ''
},
_time () {
return (this.value?.split(' ') || [])[1] || ''
},
dateFormatted () {
return this.formatDate(this._date)
},
timeFormatted () {
return this.formatTime(this._time)
},
date: {
get () {
return this._date
},
set (val) {
this.$emit('input', `${val} ${this._time}`)
}
},
time: {
get () {
return this._time
},
set (val) {
this.$emit('input', `${this._date} ${val}:00`)
}
}
},

methods: {
formatDate (date) {
if (!date) return null

const [year, month, day] = date.split('-')
return `${day}.${month}.${year}`
},
formatTime (time) {
if (!time) return null

const [hour, minute] = time.split(':')
return `${hour}:${minute}`
},
parseDate (date) {
if (!date) return null

const [day, month, year] = date.split('/')
return `${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')}`
}
}
}
</script>

<style scoped>

</style>

+ 0
- 20
client.ts/src/components/Demo.vue View File

@@ -1,20 +0,0 @@
<template>
<div>
T2: {{ get('pub_person','b77038e7-9f17-4ef2-951c-958aeba8a5b5') }}
</div>
</template>

<script>
import { getData } from '../plugins/useSDB'

export default {
name: 'Demo',
setup () {
return { ...getData() }
}
}
</script>

<style scoped>

</style>

+ 0
- 9
client.ts/src/components/base/Card.vue View File

@@ -1,9 +0,0 @@
<script>
import { VCard } from 'vuetify/lib'

export default {
name: 'Card',

extends: VCard
}
</script>

+ 0
- 77
client.ts/src/components/base/EditDialog.vue View File

@@ -1,77 +0,0 @@
<template>
<v-edit-dialog
:return-value.sync="wert"
large
persistent
:save-text="savebutton"
:cancel-text="cancelbutton"
@open="open"
@save="save"
@close="close"
>
<div style="min-width:40px;">
{{ value }}
</div>
<template #input>
<v-text-field
v-model="wert"
:label="label"
autofocus
/>
</template>
</v-edit-dialog>
</template>

<script>
export default {
name: 'EditDialog',

props: {
value: {
type: [String, Number],
required: false,
default: ''
},
label: {
type: String,
required: false,
default: ''
},
savebutton: {
type: String,
required: false,
default: 'Übernehmen'
},
cancelbutton: {
type: String,
required: false,
default: 'Abbrechen'
}
},

data: () => ({
wert: null
}),

methods: {
open () {
if (this.value === null || this.value === undefined) {
this.wert = ''
} else {
this.wert = `${this.value}`
}
},
close () {
this.wert = null
},
save () {
this.$emit('input', this.wert)
this.wert = null
}
}
}
</script>

<style scoped>

</style>

+ 0
- 76
client.ts/src/components/base/EditDialogDate.vue View File

@@ -1,76 +0,0 @@
<template>
<v-edit-dialog
:return-value.sync="wert"
large
persistent
:save-text="savebutton"
cancel-text="Abbrechen"
@open="open"
@save="save"
@close="close"
>
<div>{{ value | dateformat(dateformat) }}</div>
<template #input>
<date-selector
v-model="wert"
:label="label"
:clearable="clearable"
/>
</template>
</v-edit-dialog>
</template>

<script>
export default {
name: 'EditDialog',

props: {
value: {
type: String,
required: false,
default: undefined
},
label: {
type: String,
required: false,
default: ''
},
savebutton: {
type: String,
required: false,
default: 'Übernehmen'
},
dateformat: {
type: String,
required: false,
default: null
},
clearable: {
type: Boolean,
required: false,
default: false
}
},

data: () => ({
wert: null
}),

methods: {
open () {
this.wert = this.value
},
close () {
this.wert = null
},
save () {
this.$emit('input', this.wert)
this.wert = null
}
}
}
</script>

<style scoped>

</style>

+ 0
- 82
client.ts/src/components/base/EditDialogSelect.vue View File

@@ -1,82 +0,0 @@
<template>
<v-edit-dialog
:return-value.sync="wert"
large
persistent
:save-text="savebutton"
cancel-text="Abbrechen"
@open="open"
@save="save"
@close="close"
>
<div>{{ text }}</div>
<template #input>
<v-select
v-model="wert"
:label="label"
:items="myitems"
autofocus
clearable
/>
</template>
</v-edit-dialog>
</template>

<script>
export default {
name: 'EditDialogSelect',

props: {
value: {
type: String,
required: false,
default: undefined
},
label: {
type: String,
required: false,
default: ''
},
savebutton: {
type: String,
required: false,
default: 'Übernehmen'
},
items: {
type: undefined,
required: true
}
},

data: () => ({
wert: null
}),

computed: {
text () {
return this.myitems?.find(e => e.value === this.value)?.text
},
myitems () {
if (Array.isArray(this.items)) return this.items
return []
}
},

methods: {
open () {
this.wert = this.value
},
close () {
this.wert = null
},
save () {
this.$emit('input', this.wert)
this.wert = null
}
}
}
</script>

<style scoped>

</style>

+ 0
- 69
client.ts/src/components/base/Item.vue View File

@@ -1,69 +0,0 @@
<template>
<v-list-item
:href="href"
:rel="href && href !== '#' ? 'noopener' : undefined"
:target="href && href !== '#' ? '_blank' : undefined"
:to="item.to"
:active-class="`primary ${!isDark ? 'black' : 'white'}--text`"
>
<v-list-item-icon v-if="item.icon">
<v-icon v-text="item.icon" />
</v-list-item-icon>

<v-list-item-icon
v-else-if="text"
class="v-list-item__icon--text"
v-text="computedText"
/>

<v-list-item-content v-if="item.title || item.subtitle">
<v-list-item-title v-text="item.title" />

<v-list-item-subtitle v-text="item.subtitle" />
</v-list-item-content>
</v-list-item>
</template>

<script>
import Themeable from 'vuetify/lib/mixins/themeable'

export default {
name: 'Item',

mixins: [Themeable],

props: {
item: {
type: Object,
default: () => ({
href: undefined,
icon: undefined,
subtitle: undefined,
title: undefined,
to: undefined
})
},
text: {
type: Boolean,
default: false
}
},

computed: {
computedText () {
if (!this.item || !this.item.title) return ''

let text = ''

this.item.title.split(' ').forEach(val => {
text += val.substring(0, 1)
})

return text
},
href () {
return this.item.href || (!this.item.to ? '#' : undefined)
}
}
}
</script>

+ 0
- 142
client.ts/src/components/base/ItemGroup.vue View File

@@ -1,142 +0,0 @@
<template>
<v-list-group
:group="group"
:prepend-icon="item.icon"
:sub-group="subGroup"
append-icon="mdi-menu-down"
:color="barColor !== 'rgba(255, 255, 255, 1), rgba(255, 255, 255, 0.7)' ? 'white' : 'grey darken-1'"
>
<template #activator>
<v-list-item-icon
v-if="text"
class="v-list-item__icon--text"
v-text="computedText"
/>

<v-list-item-avatar
v-else-if="item.avatar"
class="align-self-center"
color="grey"
>
<v-img src="https://demos.creative-tim.com/material-dashboard-pro/assets/img/faces/avatar.jpg" />
</v-list-item-avatar>

<v-list-item-content>
<v-list-item-title v-text="item.title" />
<v-tooltip bottom>
<template #activator="{ on }">
<v-list-item-subtitle
v-on="on"
v-text="item.subtitle"
/>
</template>
<span>{{ item.subtitle }}</span>
</v-tooltip>
</v-list-item-content>
</template>

<template v-for="(child, i) in children">
<base-item-sub-group
v-if="child.children"
:key="`sub-group-${i}`"
:item="buildChild(child)"
/>

<base-item
v-else
:key="`item-${i}`"
:item="child"
text
/>
</template>
</v-list-group>
</template>

<script>
// Utilities
import kebabCase from 'lodash/kebabCase'
import { mapState } from 'vuex'

export default {
name: 'ItemGroup',

inheritAttrs: false,

props: {
item: {
type: Object,
default: () => ({
avatar: undefined,
group: undefined,
title: undefined,
children: []
})
},
subGroup: {
type: Boolean,
default: false
},
text: {
type: Boolean,
default: false
}
},

computed: {
...mapState(['barColor']),
children () {
return this.item.children.map(item => ({
...item,
to: !item.to ? undefined : `${this.item.group}/${item.to}`
}))
},
computedText () {
if (!this.item || !this.item.title) return ''

let text = ''

this.item.title.split(' ').forEach(val => {
text += val.substring(0, 1)
})

return text
},
group () {
return this.genGroup(this.item.children)
}
},

methods: {
genGroup (children, parentgroup) {
if (!parentgroup) {
parentgroup = this.item.group
}
return children
.filter(item => item.to || item.group)
.map(item => {
let group = `${parentgroup}/${kebabCase(item.to)}`

if (item.children) {
group = `${group}|${this.genGroup(item.children, `${parentgroup}/${item.group}`)}`
}

return group
}).join('|')
},
buildChild (child) {
return { ...child, group: `${this.item.group}/${child.group}` }
}
}
}
</script>

<style>
.v-list-group__activator p {
margin-bottom: 0;
}

.v-list-group .v-list-group__items {
padding-left: 3px;
border-left: 1px dashed red;
}
</style>

+ 0
- 24
client.ts/src/components/base/ItemSubGroup.vue View File

@@ -1,24 +0,0 @@
<template>
<base-item-group
:item="item"
:text="!item.icon"
/>
</template>

<script>
export default {
name: 'ItemSubGroup',

props: {
item: {
type: Object,
default: () => ({
avatar: undefined,
group: undefined,
title: undefined,
children: []
})
}
}
}
</script>

+ 0
- 60
client.ts/src/components/base/MaterialAlert.vue View File

@@ -1,60 +0,0 @@
<template>
<v-alert
v-bind="$attrs"
class="v-alert--material"
dark
v-on="$listeners"
>
<template
v-if="$attrs.icon"
#prepend
>
<v-icon
class="v-alert__icon elevation-6 white"
light
:color="$attrs.color"
>
{{ $attrs.icon }}
</v-icon>
</template>

<slot />

<template
v-if="$attrs.dismissible"
#close="{ toggle }"
>
<v-btn
:aria-label="$vuetify.lang.t('$vuetify.close')"
color
icon
small
@click="toggle"
>
<v-icon>
$vuetify.icons.cancel
</v-icon>
</v-btn>
</template>
</v-alert>
</template>

<script>
export default {
name: 'MaterialAlert'
}
</script>

<style lang="sass">

.v-alert--material
margin-top: 32px

.v-alert__icon
top: -36px

.v-alert__dismissible
align-self: flex-start
margin: 0 !important
padding: 0 !important
</style>

+ 0
- 176
client.ts/src/components/base/MaterialCard.vue View File

@@ -1,176 +0,0 @@
<template>
<v-card
v-bind="$attrs"
:class="classes"
class="v-card--material pa-3"
>
<div class="d-flex grow flex-wrap">
<v-avatar
v-if="avatar"
size="128"
class="mx-auto v-card--material__avatar elevation-6"
color="grey"
>
<v-img :src="avatar" />
</v-avatar>

<v-sheet
v-else
:class="{
'pa-7': !$slots.image
}"
:color="color"
:max-height="icon ? 90 : undefined"
:width="inline || icon ? 'auto' : '100%'"
elevation="6"
class="text-start v-card--material__heading mb-n6"
dark
>
<slot
v-if="$slots.heading"
name="heading"
/>

<slot
v-else-if="$slots.image"
name="image"
/>

<div
v-else-if="title && !icon"
class="display-1 font-weight-light"
v-text="title"
/>

<v-icon
v-else-if="icon"
size="32"
v-text="icon"
/>

<div
v-if="text"
class="headline font-weight-thin"
v-text="text"
/>
</v-sheet>

<div
v-if="$slots['after-heading']"
class="ml-6"
>
<slot name="after-heading" />
</div>

<v-col
v-if="hoverReveal"
cols="12"
class="text-center py-0 mt-n12"
>
<slot name="reveal-actions" />
</v-col>

<div
v-else-if="icon && title"
class="ml-4"
>
<div
class="card-title font-weight-light"
v-text="title"
/>
<div
class="card-subtitle font-weight-light font-italic grey--text"
v-text="subTitle"
/>
</div>
</div>

<slot />

<template v-if="$slots.actions">
<v-divider class="mt-2" />

<v-card-actions class="pb-0">
<slot name="actions" />
</v-card-actions>
</template>
</v-card>
</template>

<script>
export default {
name: 'MaterialCard',

props: {
avatar: {
type: String,
default: ''
},
color: {
type: String,
default: 'success'
},
hoverReveal: {
type: Boolean,
default: false
},
icon: {
type: String,
default: undefined
},
image: {
type: Boolean,
default: false
},
inline: {
type: Boolean,
default: false
},
text: {
type: String,
default: ''
},
title: {
type: String,
default: ''
},
subTitle: {
type: String,
default: ''
}
},

computed: {
classes () {
return {
'v-card--material--has-heading': this.hasHeading,
'v-card--material--hover-reveal': this.hoverReveal
}
},
hasHeading () {
return Boolean(this.$slots.heading || this.title || this.icon)
},
hasAltHeading () {
return Boolean(this.$slots.heading || (this.title && this.icon))
}
}
}
</script>

<style lang="sass">
.v-card--material
&__avatar
position: relative
top: -64px
margin-bottom: -32px

&__heading
position: relative
top: -40px
transition: .3s ease
z-index: 1

&.v-card--material--hover-reveal:hover
.v-card--material__heading
transform: translateY(-40px)
</style>

+ 0
- 95
client.ts/src/components/base/MaterialChartCard.vue View File

@@ -1,95 +0,0 @@
<template>
<base-material-card
class="v-card--material-chart"
v-bind="$attrs"
v-on="$listeners"
>
<template #heading>
<chartist
:data="data"
:event-handlers="eventHandlers"
:options="options"
:ratio="ratio"
:responsive-options="responsiveOptions"
:type="type"
style="max-height: 150px;"
/>
</template>

<slot
slot="reveal-actions"
name="reveal-actions"
/>

<slot />

<slot
slot="actions"
name="actions"
/>
</base-material-card>
</template>

<script>
export default {
name: 'MaterialChartCard',

inheritAttrs: false,

props: {
data: {
type: Object,
default: () => ({})
},
eventHandlers: {
type: Array,
default: () => ([])
},
options: {
type: Object,
default: () => ({})
},
ratio: {
type: String,
default: undefined
},
responsiveOptions: {
type: Array,
default: () => ([])
},
type: {
type: String,
required: true,
validator: v => ['Bar', 'Line', 'Pie'].includes(v)
}
}
}
</script>

<style lang="sass">
.v-card--material-chart
p
color: #999

.v-card--material__heading
max-height: 185px

.ct-label
color: inherit
opacity: .7
font-size: 0.975rem
font-weight: 100

.ct-grid
stroke: rgba(255, 255, 255, 0.2)

.ct-series-a .ct-point,
.ct-series-a .ct-line,
.ct-series-a .ct-bar,
.ct-series-a .ct-slice-donut
stroke: rgba(255,255,255,.8)

.ct-series-a .ct-slice-pie,
.ct-series-a .ct-area
fill: rgba(255,255,255,.4)
</style>

+ 0
- 164
client.ts/src/components/base/MaterialDialog.vue View File

@@ -1,164 +0,0 @@
<template>
<v-dialog
v-model="show"
persistent
scrollable
styles="overflow:visible;"
content-class="v-dialog--material"
@keydown.esc="$emit('esc')"
>
<base-material-card
:avatar="avatar"
:color="color"
:icon="icon"
:image="image"
:text="text"
:title="title"
:sub-title="subTitle"
>
<v-card-text>
<slot />
</v-card-text>
<template #actions>
<v-spacer />
<template v-for="(a,i) in actions">
<v-btn
v-if="a === 'ok'"
:key="`${a}-${i}`"
@click="$emit(a)"
>
OK
</v-btn>
<v-btn
v-if="a === 'cancel'"
:key="`${a}-${i}`"
@click="$emit('close')"
>
Abbrechen
</v-btn>
<v-btn
v-if="a === 'close'"
:key="`${a}-${i}`"
@click="$emit('close')"
>
Schließen
</v-btn>
<v-btn
v-if="a === 'save'"
:key="`${a}-${i}`"
@click="$emit(a)"
>
Speichern
</v-btn>
<v-btn
v-if="a === 'del'"
:key="`${a}-${i}`"
@click="$emit(a)"
>
Löschen
</v-btn>
</template>
</template>
</base-material-card>
</v-dialog>
</template>

<script>
export default {
name: 'MaterialCard',

inheritAttrs: false,

props: {
value: {
type: Boolean,
required: true,
default: false
},
avatar: {
type: String,
default: ''
},
color: {
type: String,
default: 'success'
},
hoverReveal: {
type: Boolean,
default: false
},
icon: {
type: String,
default: undefined
},
image: {
type: Boolean,
default: false
},
inline: {
type: Boolean,
default: false
},
text: {
type: String,
default: ''
},
title: {
type: String,
default: ''
},
subTitle: {
type: String,
default: ''
},
actions: {
type: Array,
default: () => []
}
},

computed: {
show: {
get () {
return this.value
},
set (val) {
this.$emit('input', val)
}
},
classes () {
return {
'v-card--material--has-heading': this.hasHeading,
'v-card--material--hover-reveal': this.hoverReveal
}
},
hasHeading () {
return Boolean(this.$slots.heading || this.title || this.icon)
},
hasAltHeading () {
return Boolean(this.$slots.heading || (this.title && this.icon))
}
}
}
</script>

<style lang="sass">
.v-card--material
&__avatar
position: relative
top: -64px
margin-bottom: -32px

&__heading
position: relative
top: -40px
transition: .3s ease
z-index: 1

&.v-card--material--hover-reveal:hover
.v-card--material__heading
transform: translateY(-40px)

.v-dialog--material
overflow-y: visible !important
</style>

+ 0
- 70
client.ts/src/components/base/MaterialDropdown.vue View File

@@ -1,70 +0,0 @@
<template>
<v-menu
v-model="value"
:transition="transition"
offset-y
v-bind="$attrs"
>
<template #activator="{ attrs, on }">
<v-btn
:color="color"
default
min-width="200"
rounded
v-bind="attrs"
v-on="on"
>
<slot />

<v-icon>
mdi-{{ value ? 'menu-up' : 'menu-down' }}
</v-icon>
</v-btn>
</template>

<v-sheet>
<v-list dense>
<v-list-item
v-for="(item, i) in items"
:key="i"
@click="$(`click:action-${item.id}`)"
>
<v-list-item-content>
<v-list-item-title v-text="item.text" />
</v-list-item-content>
</v-list-item>
</v-list>
</v-sheet>
</v-menu>
</template>

<script>
// Mixins
import Proxyable from 'vuetify/lib/mixins/proxyable'

export default {
name: 'MaterialDropdown',

mixins: [Proxyable],

props: {
color: {
type: String,
default: 'primary'
},
items: {
type: Array,
default: () => ([
{
id: undefined,
text: undefined
}
])
},
transition: {
type: String,
default: 'scale-transition'
}
}
}
</script>

+ 0
- 66
client.ts/src/components/base/MaterialSnackbar.vue View File

@@ -1,66 +0,0 @@
<template>
<v-snackbar
:class="classes"
:value="value"
v-bind="{
...$attrs,
...$props,
'color': 'transparent'
}"
@change="$emit('change', $event)"
>
<base-material-alert
:color="color"
:dismissible="dismissible"
:type="type"
class="ma-0"
dark
>
<slot />
</base-material-alert>
</v-snackbar>
</template>
<script>
// Components
import { VSnackbar } from 'vuetify/lib'

export default {
name: 'BaseMaterialSnackbar',

extends: VSnackbar,

props: {
dismissible: {
type: Boolean,
default: true
},
type: {
type: String,
default: ''
}
},

computed: {
classes () {
return {
...VSnackbar.options.computed.classes.call(this),
'v-snackbar--material': true
}
}
}
}
</script>

<style lang="sass">
.v-snackbar--material
margin-top: 32px
margin-bottom: 32px

.v-alert--material,
.v-snack__wrapper
border-radius: 4px

.v-snack__content
overflow: visible
padding: 0
</style>

+ 0
- 113
client.ts/src/components/base/MaterialStatsCard.vue View File

@@ -1,113 +0,0 @@
<template>
<base-material-card
:icon="icon"
class="v-card--material-stats"
v-bind="$attrs"
v-on="$listeners"
>
<template #after-heading>
<div class="ml-auto text-right">
<div
class="body-3 grey--text font-weight-light"
v-text="title"
/>

<h3 class="display-2 font-weight-light text--primary">
{{ value }} <small>{{ smallValue }}</small>
</h3>
</div>
</template>

<v-col
cols="12"
class="px-0"
>
<v-divider />
</v-col>

<v-icon
:color="subIconColor"
size="16"
class="ml-2 mr-1"
>
{{ subIcon }}
</v-icon>

<span
:class="subTextColor"
class="caption grey--text font-weight-light"
v-text="subText"
/>
</base-material-card>
</template>

<script>
import Card from './Card'

export default {
name: 'MaterialStatsCard',

inheritAttrs: false,

props: {
...Card.props,
icon: {
type: String,
required: true
},
subIcon: {
type: String,
default: undefined
},
subIconColor: {
type: String,
default: undefined
},
subTextColor: {
type: String,
default: undefined
},
subText: {
type: String,
default: undefined
},
title: {
type: String,
default: undefined
},
value: {
type: String,
default: undefined
},
smallValue: {
type: String,
default: undefined
}
}
}
</script>

<style lang="sass">
.v-card--material-stats
display: flex
flex-wrap: wrap
position: relative

> div:first-child
justify-content: space-between

.v-card
border-radius: 4px
flex: 0 1 auto

.v-card__text
display: inline-block
flex: 1 0 calc(100% - 120px)
position: absolute
top: 0
right: 0
width: 100%

.v-card__actions
flex: 1 0 100%
</style>

+ 0
- 43
client.ts/src/components/base/MaterialTabs.vue View File

@@ -1,43 +0,0 @@
<template>
<v-tabs
v-model="internalValue"
:active-class="`${color} ${$vuetify.theme.dark ? 'black' : 'white'}--text`"
class="v-tabs--pill"
hide-slider
v-bind="$attrs"
>
<slot />

<slot name="items" />
</v-tabs>
</template>

<script>
// Mixins
import Proxyable from 'vuetify/lib/mixins/proxyable'

export default {
name: 'MaterialTabs',

mixins: [Proxyable],

props: {
color: {
type: String,
default: 'primary'
}
}
}
</script>

<style lang="sass">
.v-tabs--pill
.v-tab,
.v-tab:before
border-radius: 24px

&.v-tabs--icons-and-text
.v-tab,
.v-tab:before
border-radius: 4px
</style>

+ 0
- 76
client.ts/src/components/base/MaterialTestimony.vue View File

@@ -1,76 +0,0 @@
<template>
<v-card class="text-center v-card--testimony">
<div class="pt-6">
<v-icon
color="black"
x-large
>
mdi-format-quote-close
</v-icon>
</div>

<v-card-text
class="display-1 font-weight-light font-italic mb-3"
v-text="blurb"
/>

<div
class="display-2 font-weight-light mb-2"
v-text="author"
/>

<div
class="body-2 text-uppercase grey--text"
v-text="handle"
/>

<v-avatar
class="elevation-12"
color="grey"
size="100"
>
<v-img
:alt="`${author} Testimonial`"
:src="avatar"
/>
</v-avatar>

<div />
</v-card>
</template>

<script>
export default {
name: 'BaseMaterialTestimony',

props: {
author: {
type: String,
default: ''
},
avatar: {
type: String,
default: 'https://demos.creative-tim.com/material-dashboard-pro/assets/img/faces/card-profile1-square.jpg'
},
blurb: {
type: String,
default: ''
},
handle: {
type: String,
default: ''
}
}
}
</script>

<style lang="sass">
.v-card--testimony
padding-bottom: 72px
margin-bottom: 64px

.v-avatar
position: absolute
left: calc(50% - 64px)
top: calc(100% - 64px)
</style>

+ 0
- 110
client.ts/src/components/base/MaterialWizard.vue View File

@@ -1,110 +0,0 @@
<template>
<v-card
class="v-card--wizard"
elevation="12"
max-width="700"
>
<v-card-title class="justify-center display-2 font-weight-light pt-5">
Build your profile
</v-card-title>

<div class="text-center display-1 grey--text font-weight-light mb-6">
This information will let us know more about you.
</div>

<v-tabs
ref="tabs"
v-model="internalValue"
background-color="green lighten-5"
color="white"
grow
slider-size="50"
>
<v-tabs-slider
class="mt-1"
color="success"
/>

<v-tab
v-for="(item, i) in items"
:key="i"
:ripple="false"
:disabled="!availableSteps.includes(i)"
>
{{ item }}
</v-tab>
</v-tabs>

<div class="my-6" />

<v-card-text>
<v-tabs-items v-model="internalValue">
<slot />
</v-tabs-items>
</v-card-text>

<v-card-actions class="pb-4 pa-4">
<v-btn
:disabled="internalValue === 0"
class="white--text"
color="grey darken-2"
min-width="125"
@click="$emit('click-prev')"
>
Previous
</v-btn>

<v-spacer />

<v-btn
:disabled="!availableSteps.includes(internalValue + 1)"
color="success"
min-width="100"
@click="$emit('click-next')"
>
{{ internalValue === items.length - 1 ? 'Finish' : 'Next' }}
</v-btn>
</v-card-actions>
</v-card>
</template>

<script>
// Mixins
import Proxyable from 'vuetify/lib/mixins/proxyable'

export default {
name: 'BaseMaterialWizard',

mixins: [Proxyable],

props: {
availableSteps: {
type: Array,
default: () => ([])
},
items: {
type: Array,
default: () => ([])
}
}
}
</script>

<style lang="sass">
.v-card--wizard
overflow: visible

.v-tabs-bar
height: 56px
padding: 0 8px

.v-tabs-slider-wrapper
overflow: visible

.v-tabs-slider
border-radius: 4px

.v-tabs-slider-wrapper
contain: initial
z-index: 0
</style>

+ 0
- 455
client.ts/src/components/base/NumberInput.vue View File

@@ -1,455 +0,0 @@
<template>
<div
class="number-input"
:class="{
'number-input--inline': inline,
'number-input--center': center,
'number-input--controls': controls,
[`number-input--${size}`]: size,
}"
v-on="listeners"
>
<button
v-if="controls"
class="number-input__button number-input__button--minus"
type="button"
tabindex="-1"
:disabled="disabled || readonly || !decreasable"
@click="decrease"
/>
<input
:ref="myref"
class="number-input__input"
v-bind="attrs"
:name="name"
:value="formatted"
:min="min"
:max="max"
:step="step"
:readonly="readonly || !inputtable"
:disabled="disabled || (!decreasable && !increasable)"
:placeholder="placeholder"
:tabindex="tabindex"
:autofocus="autofocus"
autocomplete="off"
@change="change"
@paste="paste"
@keydown.down="decrease"
@keydown.up="increase"
>
<button
v-if="controls"
class="number-input__button number-input__button--plus"
type="button"
tabindex="-1"
:disabled="disabled || readonly || !increasable"
@click="increase"
/>
</div>
</template>

<script>
const isNaN = Number.isNaN || window.isNaN
const REGEXP_NUMBER = /^-?(?:\d+|\d+\.\d+|\.\d+)(?:[eE][-+]?\d+)?$/
const REGEXP_DECIMALS = /\.\d*(?:0|9){10}\d*$/
const normalizeDecimalNumber = (value, times = 100) => (
REGEXP_DECIMALS.test(value) ? (Math.round(value * times) / times) : value
)

export default {
name: 'NumberInput',

model: {
event: 'change'
},

props: {
attrs: {
type: Object,
default: undefined
},

center: Boolean,
controls: Boolean,
disabled: Boolean,

inputtable: {
type: Boolean,
default: true
},

inline: Boolean,

max: {
type: Number,
default: Infinity
},

min: {
type: Number,
default: -Infinity
},

name: {
type: String,
default: undefined
},

placeholder: {
type: String,
default: undefined
},

readonly: Boolean,
rounded: Boolean,

size: {
type: String,
default: undefined
},

step: {
type: Number,
default: 1
},

value: {
type: Number,
default: NaN
},

decimals: {
type: Number,
default: NaN
},

tabindex: {
type: Number,
default: undefined
},

autofocus: Boolean,

myref: {
type: String,
default: 'input'
}
},

data () {
return {
currentValue: NaN
}
},

computed: {
/**
* Indicate if the value is increasable.
* @returns {boolean} Return `true` if it is decreasable, else `false`.
*/
increasable () {
const num = this.currentValue

return isNaN(num) || num < this.max
},

/**
* Indicate if the value is decreasable.
* @returns {boolean} Return `true` if it is decreasable, else `false`.
*/
decreasable () {
const num = this.currentValue

return isNaN(num) || num > this.min
},

/**
* Filter listeners
* @returns {Object} Return filtered listeners.
*/
listeners () {
const listeners = { ...this.$listeners }

delete listeners.change

return listeners
},

formatted () {
return isNaN(this.currentValue) ? '' : (isNaN(this.decimals) ? this.currentValue : parseFloat(this.currentValue).toFixed(this.decimals))
}
},

watch: {
value: {
immediate: true,
handler (newValue, oldValue) {
if (
// Avoid triggering change event when created
!(isNaN(newValue) && typeof oldValue === 'undefined') &&

// Avoid infinite loop
newValue !== this.currentValue
) {
this.setValue(newValue)
}
}
}
},

methods: {
/**
* Change event handler.
* @param {string} value - The new value.
*/
change (event) {
this.setValue(Math.min(this.max, Math.max(this.min, parseFloat(event.target.value.replace(',', '.')))))
},

/**
* Paste event handler.
* @param {Event} event - Event object.
*/
paste (event) {
const clipboardData = event.clipboardData || window.clipboardData

if (clipboardData && !REGEXP_NUMBER.test(clipboardData.getData('text'))) {
event.preventDefault()
}
},

/**
* Decrease the value.
*/
decrease () {
if (this.decreasable) {
let { currentValue } = this

if (isNaN(currentValue)) {
currentValue = 0
}

this.setValue(Math.min(this.max, Math.max(
this.min,
normalizeDecimalNumber(currentValue - this.step)
)))
}
},

/**
* Increase the value.
*/
increase () {
if (this.increasable) {
let { currentValue } = this

if (isNaN(currentValue)) {
currentValue = 0
}

this.setValue(Math.min(this.max, Math.max(
this.min,
normalizeDecimalNumber(currentValue + this.step)
)))
}
},

/**
* Set new value and dispatch change event.
* @param {number} value - The new value to set.
*/
setValue (value) {
const oldValue = this.currentValue
let newValue = this.rounded ? Math.round(value) : value

if (this.min <= this.max) {
newValue = Math.min(this.max, Math.max(this.min, newValue))
}

this.currentValue = newValue

if (newValue === oldValue) {
// Force to override the number in the input box (#13).
this.$refs[this.ref].value = newValue
}

this.$emit('change', newValue, oldValue)
}
}
}
</script>

<style lang="scss" scoped>
.number-input {
display: block;
font-size: 0;
max-width: 100%;
overflow: hidden;
position: relative;

&__button {
background-color: #fff;
border: 0;
border-radius: 0.25rem;
bottom: 1px;
position: absolute;
top: 1px;
width: 2.5rem;
z-index: 1;

&:focus {
outline: none;
}

&:hover {
&::before,
&::after {
background-color: #0074d9;
}
}

&:disabled {
opacity: 0.65;

&::before,
&::after {
background-color: #ddd;
}
}

&::before,
&::after {
background-color: #111;
content: "";
left: 50%;
position: absolute;
top: 50%;
transform: translate(-50%, -50%);
transition: background-color 0.15s;
}

&::before {
height: 1px;
width: 50%;
}

&::after {
height: 50%;
width: 1px;
}

&--minus {
border-bottom-right-radius: 0;
border-right: 1px solid #ddd;
border-top-right-radius: 0;
left: 1px;

&::after {
visibility: hidden;
}
}

&--plus {
border-bottom-left-radius: 0;
border-left: 1px solid #ddd;
border-top-left-radius: 0;
right: 1px;
}
}

&__input {
-moz-appearance: textfield;
background-color: #fff;
border: 1px solid #ddd;
border-radius: 0.25rem;
display: block;
font-size: 1rem;
line-height: 1.5;
max-width: 100%;
min-height: 1.5rem;
min-width: 3rem;
padding: 0.4375rem 0.875rem;
transition: border-color 0.15s;
width: 100%;

&::-webkit-outer-spin-button,
&::-webkit-inner-spin-button {
-webkit-appearance: none;
}

&:focus {
border-color: #0074d9;
outline: none;
}

&:disabled,
&[readonly] {
background-color: #f8f8f8;
}
}

&--inline {
display: inline-block;

& > input {
display: inline-block;
width: 12.5rem;
}
}

&--center {
& > input {
text-align: center;
}
}

&--controls {
& > input {
padding-left: 3.375rem;
padding-right: 3.375rem;
}
}

&--small {
& > input {
border-radius: 0.1875rem;
font-size: 0.875rem;
padding: 0.25rem 0.5rem;
}

&.number-input--inline > input {
width: 10rem;
}

&.number-input--controls > button {
width: 2rem;
}

&.number-input--controls > input {
padding-left: 2.5rem;
padding-right: 2.5rem;
}
}

&--large {
& > input {
border-radius: 0.3125rem;
font-size: 1.25rem;
padding: 0.5rem 1rem;
}

&.number-input--inline > input {
width: 15rem;
}

&.number-input--controls > button {
width: 3rem;
}

&.number-input--controls > input {
padding-left: 4rem;
padding-right: 4rem;
}
}
}
</style>

+ 0
- 34
client.ts/src/components/base/Subheading.vue View File

@@ -1,34 +0,0 @@
<template>
<div class="display-2 font-weight-light col col-12 text-left text--primary pa-0 mb-8">
<h5 class="font-weight-light">
{{ subheading }}
<template v-if="text">
<span
class="subtitle-1"
v-text="text"
/>
</template>
</h5>

<div class="pt-2">
<slot />
</div>
</div>
</template>

<script>
export default {
name: 'Subheading',

props: {
subheading: {
type: String,
default: ''
},
text: {
type: String,
default: ''
}
}
}
</script>

+ 0
- 40
client.ts/src/components/base/VComponent.vue View File

@@ -1,40 +0,0 @@
<template>
<section class="mb-12 text-center">
<h1
class="font-weight-light mb-2 headline"
v-text="`Vuetify ${heading}`"
/>

<span
class="font-weight-light subtitle-1"
>
Please checkout the
<a
:href="`https://vuetifyjs.com/${link}`"
rel="noopener"
target="_blank"
class="secondary--text"
style="text-decoration:none;"
>
full documentation
</a>
</span>
</section>
</template>

<script>
export default {
name: 'VComponent',

props: {
heading: {
type: String,
default: ''
},
link: {
type: String,
default: ''
}
}
}
</script>

+ 0
- 17
client.ts/src/components/e403.vue View File

@@ -1,17 +0,0 @@
<template>
<v-container>
<v-card-title class="red--text">
403: Kein Zugriff
</v-card-title>
</v-container>
</template>

<script>
export default {
name: 'E403'
}
</script>

<style scoped>

</style>

+ 0
- 27
client.ts/src/main.ts View File

@@ -1,27 +0,0 @@
import Vue from 'vue'
import App from './App.vue'
import router from './plugins/router'
import store from './plugins/store'
import vuetify from './plugins/vuetify'

import '@mdi/font/css/materialdesignicons.css'
import '@fortawesome/fontawesome-pro/css/all.css'

import './plugins/cookies'
import './plugins/compositionAPI'
import './plugins/base'

Vue.component('E403', () => import('./components/e403.vue'))
Vue.component('DateSelector', () => import('./components/DateSelector.vue'))
Vue.component('DateTimeSelector', () => import('./components/DateTimeSelector.vue'))
Vue.component('Confirm', () => import('./components/Confirm.vue'))

Vue.config.productionTip = false

new Vue({
router,
store,
// @ts-ignore
vuetify,
render: h => h(App)
}).$mount('#app')

+ 0
- 19
client.ts/src/plugins/base.ts View File

@@ -1,19 +0,0 @@
import Vue from 'vue'
// @ts-ignore
import upperFirst from 'lodash/upperFirst'
// @ts-ignore
import camelCase from 'lodash/camelCase'

const requireComponent = require.context(
'@/components/base', true, /\.vue$/
)

requireComponent.keys().forEach(fileName => {
const componentConfig = requireComponent(fileName)

const componentName = upperFirst(
camelCase(fileName.replace(/^\.\//, '').replace(/\.\w+$/, ''))
)

Vue.component(`Base${componentName}`, componentConfig.default || componentConfig)
})

+ 0
- 4
client.ts/src/plugins/compositionAPI.ts View File

@@ -1,4 +0,0 @@
import Vue from 'vue'
import VueCompositionAPI from '@vue/composition-api'

Vue.use(VueCompositionAPI)

+ 0
- 4
client.ts/src/plugins/cookies.ts View File

@@ -1,4 +0,0 @@
import Vue from 'vue'
import VueCookies from 'vue-cookies'

Vue.use(VueCookies)

+ 0
- 82
client.ts/src/plugins/graphql.ts View File

@@ -1,82 +0,0 @@
// @ts-ignore
import { v4 as uuid } from 'uuid'
import { SubscriptionClient } from 'subscriptions-transport-ws'
import ApolloClient from 'apollo-client'
import VueApollo from 'vue-apollo'
import { defaultDataIdFromObject, InMemoryCache } from 'apollo-cache-inmemory'
// @ts-ignore
import * as Cookie from 'js-cookie'
import { ref } from '@vue/composition-api'
import gql from 'graphql-tag'

export const clientId = uuid()

const server = process.env.NODE_ENV === 'production' ? 'wss://turnenaufzeit.de/gql' : 'ws://localhost:3000/graphql'

const client = new SubscriptionClient(server, {
reconnect: true,
connectionParams: () => ({
clientId,
token: Cookie.get('token')
})
})

const graphQL = new ApolloClient({
// @ts-ignore
link: client,
cache: new InMemoryCache({
dataIdFromObject: object => {
switch (object.__typename) {
default: return defaultDataIdFromObject(object)
}
}
}),
defaultOptions: {
watchQuery: {
fetchPolicy: 'cache-and-network'
},
query: {
fetchPolicy: 'no-cache'
}
}
})

export default new VueApollo({
defaultClient: graphQL
})

export const useGraphQL = (context: any): any => {
const connected = ref(client?.client?.readyState === 1)

client.on('connecting', () => { connected.value = false })
client.on('connected', () => { connected.value = true })
client.on('reconnecting', () => { connected.value = false })
client.on('reconnected', () => { connected.value = true })
client.on('disconnected', () => { connected.value = false })
client.on('error', () => { connected.value = false })

const login = async (email: string, passwort: string) => {
let query = `login(email: "${email}", passwort: "${passwort}")`
if (!email || !passwort) {
query = `login(token: "${Cookie.get('token')}")`
}

const { data } = await graphQL.mutate({
mutation: gql`mutation { ${query} {
_id
token
givenName
familyName
adminOf { _id name plz ort }
}}`
})

console.log(data)

context.root.$store.commit('SET_PROFILE', data.login)

Cookie.set('token', data?.login?.token, '12h', null, null, null, 'strict')
}

return { connected, login }
}

+ 0
- 20
client.ts/src/plugins/router.ts View File

@@ -1,20 +0,0 @@
import Vue from 'vue'
import VueRouter, { RouteConfig } from 'vue-router'

Vue.use(VueRouter)

const routes: Array<RouteConfig> = [
{
path: '/',
name: 'Home',
component: () => import('../views/Index.vue')
}
]

const router = new VueRouter({
mode: 'hash',
base: process.env.BASE_URL,
routes
})

export default router

+ 0
- 38
client.ts/src/plugins/store.ts View File

@@ -1,38 +0,0 @@
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

const state: { profile: any, messages: string[], drawer: boolean, snackbar: {visible: boolean, text: string } } = {
profile: null,
messages: [],
drawer: true,
snackbar: {
visible: false,
text: ''
}
}

export default new Vuex.Store({
state,
mutations: {
SET_DRAWER (state, payload) {
state.drawer = payload
},
OPEN_SNACKBAR (state, text) {
state.snackbar.text = text
state.snackbar.visible = true
},
CLOSE_SNACKBAR (state) {
state.snackbar.visible = false
}
},
actions: {
},
modules: {
},
getters: {
profile: (state) => state.profile || {},
isMaster: (state) => !!state.profile?.master
}
})

+ 0
- 31
client.ts/src/plugins/vuetify.ts View File

@@ -1,31 +0,0 @@
import Vue from 'vue'
// @ts-ignore
import Vuetify from 'vuetify/lib/framework'
import de from 'vuetify/src/locale/de'

import '@/sass/overrides.sass'

Vue.use(Vuetify)

const theme = {
primary: '#FF041D',
secondary: '#9C27b0',
accent: '#9C27b0',
info: '#00CAE3'
}

export default new Vuetify({
lang: {
locales: { de },
current: 'de'
},
icons: {
iconfont: 'fa'
},
theme: {
themes: {
dark: theme,
light: theme
}
}
})

+ 0
- 55
client.ts/src/sass/overrides.sass View File

@@ -1,55 +0,0 @@
// =========================================================
// * Vuetify Material Dashboard PRO - v2.1.0
// =========================================================
//
// * Product Page: https://www.creative-tim.com/product/vuetify-material-dashboard-pro
// * Copyright 2019 Creative Tim (https://www.creative-tim.com)
//
// * Coded by Creative Tim
//
// =========================================================
//
// * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

// Creative Tim refine style code
@import variables
@import vuetify-material/sidebar
@import vuetify-material/appbar
@import vuetify-material/buttons
@import vuetify-material/pagination
@import vuetify-material/badge
@import vuetify-material/footer
@import vuetify-material/view
@import vuetify-material/settings
@import vuetify-material/card
@import vuetify-material/table
@import vuetify-material/vectormap
@import vuetify-material/tab
@import vuetify-material/notification
@import vuetify-material/modal
@import vuetify-material/form
@import vuetify-material/map
@import vuetify-material/timeline
@import vuetify-material/chip
@import vuetify-material/calendar
@import vuetify-material/pages
@import vuetify-material/rtl

.v-btn.v-size--default,
.v-btn.v-size--large
&:not(.v-btn--icon):not(.v-btn--fab)
padding: 0 30px !important

.theme--light.v-list-item .v-list-item__action-text,
.theme--light.v-list-item .v-list-item__subtitle
color: #999

.theme--light.v-text-field>.v-input__control>.v-input__slot:before
border-color: #d2d2d2

.v-label.v-label,
.v-alert.v-alert
font-size: $font-size-root

.theme--light .v-content
background-color: #eee

+ 0
- 34
client.ts/src/sass/variables.scss View File

@@ -1,34 +0,0 @@
$font-size-root: 14px;
$sheet-border-radius: 4px;
$list-item-title-font-size: 0.929rem;
$list-item-dense-title-font-size: 0.929rem;
$list-item-dense-title-font-weight: initial;
$fab-icon-sizes: ( small: 20 );
$btn-font-sizes: ( default: 1rem, large: 1rem );
$btn-sizes: ( default: 41, large: 54 );
$btn-letter-spacing: 0;
$btn-font-weight: 400;
$card-text-font-size: 16px;

$headings: (
'h1': (
'size': 3.3125rem,
'line-height': 1.15em
),
'h2': (
'size': 2.25rem,
'line-height': 1.5em
),
'h3': (
'size': 1.5625rem,
'line-height': 1.4em
),
'h4': (
'size': 1.125rem,
'line-height': 1.4em
),
'h5': ( 'size': 1.0625rem ),
'h6': ( 'size': .75rem ),
'subtitle-2': ( 'size': 1rem ),
'overline': ( 'letter-spacing': 0 )
);

+ 0
- 54
client.ts/src/sass/vuetify-material/_appbar.sass View File

@@ -1,54 +0,0 @@
#app-bar
.v-badge__badge
font-size: 9px
padding: 5px 6px

// -----------------------
.v-toolbar__content,
.v-toolbar__extension
padding: 0px 15px 0 31px

.v-sheet
.v-toolbar__content
.v-btn.v-size--default:not(.v-btn--icon):not(.v-btn--fab),
.v-btn.v-size--large:not(.v-btn--icon):not(.v-btn--fab)
margin-bottom: 5px
padding: 10px 15px !important
.theme--light.v-btn:not(.v-btn--flat):not(.v-btn--text):not(.v-btn--outlined)
background-color: #fff
.v-icon
color: #999

.theme--light.v-btn:not(.v-btn--flat):not(.v-btn--text):not(.v-btn--outlined)
background-color: #fff
margin-right: 17px
margin-bottom: 2px

.theme--light.v-btn:not(.v-btn--flat):not(.v-btn--text):not(.v-btn--outlined):hover
background-color: #fff

.v-toolbar__content
height: 75px

.v-toolbar__content .v-btn--flat
.v-icon
margin-right: 3px

.theme--light.v-label
color: rgba(0, 0, 0, 0.3)

.v-menu__content .v-list--nav
padding: .3125rem 0
border-radius: 4px
.v-list-item
padding: 10px 20px
margin: 0 .3125rem
margin-bottom: 0px !important
min-height: 40px
border-radius: 2px
.v-list-item__title
font-weight: 400
font-size: 13px

.v-navigation-drawer .v-icon.v-icon
font-size: 24px

+ 0
- 1
client.ts/src/sass/vuetify-material/_badge.sass View File

@@ -1 +0,0 @@


+ 0
- 67
client.ts/src/sass/vuetify-material/_buttons.sass View File

@@ -1,67 +0,0 @@
.v-btn.v-size--default
font-size: .85rem

.v-icon.v-icon
font-size: 20px

.v-btn__content .v-icon--left
margin-right: 4px

.v-sheet button.v-btn.v-size--default:not(.v-btn--icon):not(.v-btn--fab)
padding: 12px 30px !important

.theme--light.v-btn:not(.v-btn--flat):not(.v-btn--text):not(.v-btn--outlined)
background-color: #999
color: #fff
&:hover
background-color: #999
color: #fff

.v-btn.white
.v-btn__content
color: #999

.v-sheet .v-btn.v-size--large:not(.v-btn--icon):not(.v-btn--fab)
padding: 18px 36px !important

.v-btn--fab.v-size--small
height: 41px
width: 41px

.v-btn:not(.v-btn--text):not(.v-btn--outlined):hover:before
opacity: 0

.v-btn:not(.v-btn--text):not(.v-btn--outlined):focus:before
opacity: 0

.v-btn.v-size--default:not(.v-btn--icon):not(.v-btn--fab),
.v-btn.v-size--large:not(.v-btn--icon):not(.v-btn--fab)
padding: 10px 15px !important

// Button group

.v-item-group
.v-btn:not(.v-btn--flat):not(.v-btn--text):not(.v-btn--outlined)
margin-right: 0

.v-btn-toggle
.v-btn
opacity: 1

.v-btn-toggle > .v-btn.v-size--default
height: inherit

.theme--light.v-btn-toggle .v-btn.v-btn
border-color: #999 !important
&.primary
border-color: #e91e63 !important
&.secondary
border-color: #9c27b0 !important
&.success
border-color: #4caf50 !important
&.warning
border-color: #fb8c00 !important
&.error
border-color: #ff5252 !important
&.info
border-color: #00cae3 !important

+ 0
- 30
client.ts/src/sass/vuetify-material/_calendar.sass View File

@@ -1,30 +0,0 @@
#calendar
.v-sheet .v-toolbar__content .theme--light.v-btn:not(.v-btn--flat):not(.v-btn--text):not(.v-btn--outlined) .v-icon
color: #fff

.v-calendar .v-event.v-event-end
width: 100

.v-calendar-weekly__head-weekday
font-weight: 700

.theme--light
.v-calendar-weekly__head-weekday
&.v-present
color: #333 !important
.v-calendar-weekly__day-label
.v-btn.transparent
margin-right: 0 !important
.v-btn__content
color: #333 !important
.v-btn--fab
margin-right: 0 !important
.v-btn__content
color: #fff !important

.theme--light.v-calendar-weekly .v-calendar-weekly__head-weekday
border-right: none
border-bottom: #e0e0e0 1px solid

.theme--light.v-calendar-weekly .v-calendar-weekly__head-weekday.v-outside
background: #fff

+ 0
- 107
client.ts/src/sass/vuetify-material/_card.sass View File

@@ -1,107 +0,0 @@
.v-card
border-radius: 6px
margin-top: 30px
margin-bottom: 15px

.card-title
font-size: 18px

.v-card--material__heading
top: -30px

.subtitle-1
color: hsla(0,0%,100%,.8)

.display-2
font-size: 18px !important

.caption
font-size: 12px !important
letter-spacing: 0 !important

.v-card__actions
padding-top: 15px
.display-2
font-size: 18px !important

.v-divider
border-color: #eee

.ct-label
font-size: 14px

.v-card--material-chart .v-card--material__heading .ct-label
font-weight: 300


.v-btn--icon.v-size--default .v-icon,
.v-btn--fab.v-size--default .v-icon
font-size: 18px

.v-card--material .v-image
.v-image__image
border-radius: 6px

.v-card__title
font-size: 18px
padding-top: 7px
padding-bottom: 2px

.theme--light
.v-card > .v-card__text
color: #333
.card-title
color: #3c4858

.theme--dark
.card-title
color: #fff


.v-timeline-item .v-card
margin-top: 0

.v-card--wizard
.v-tabs-bar
height: 42px
.v-card__actions
.v-btn
margin-right: 0 !important
.v-tabs .v-tab--active:hover::before, .theme--light.v-tabs .v-tab--active::before
opacity: 0
.v-tabs .v-tab:hover::before
opacity: 0

.v-card--plan
.body-2
font-weight: 500
letter-spacing: 0 !important
margin-top: 10px
margin-bottom: 8px
.display-2
margin-top: 30px

.v-card__text
color: #999
margin-bottom: 16px
.v-btn
margin-right: 0 !important
.v-avatar
margin-top: 10px

.v-card--testimony
.v-card__text
color: #999 !important

.display-2
font-size: 18px !important

.body-2
font-weight: 500
font-size: 12px !important

.v-avatar
left: calc(50% - 50px)

.ct-square:before
float: none

+ 0
- 9
client.ts/src/sass/vuetify-material/_chip.sass View File

@@ -1,9 +0,0 @@
.v-chip.v-size--small
height: 20px

.v-chip__content
font-size: 10px
font-weight: 500
.v-chip__close
font-size: 15px
margin-top: -1px

+ 0
- 29
client.ts/src/sass/vuetify-material/_footer.sass View File

@@ -1,29 +0,0 @@
.v-footer
padding: 20px 0 20px 4px
border-top: 1px solid #e7e7e7 !important
position: relative
a
padding: 15px 18px 15px 16px
font-size: 12px !important
.body-1
font-size: 16px !important
padding-right: 18px
letter-spacing: 0px !important
a
color: #9c27b0 !important
padding: 0
text-transform: inherit !important
font-size: 16px !important
font-weight: 300 !important
.v-icon
margin-top: -3px

&.v-footer--absolute
position: absolute !important

.theme--light.v-footer
background-color: transparent
.body-1
color: #3c4858
.v-icon
color: #3c4858

+ 0
- 45
client.ts/src/sass/vuetify-material/_form.sass View File

@@ -1,45 +0,0 @@
.v-input--selection-controls__input
margin-right: 0

.v-input--selection-controls.v-input .v-label
font-weight: 400

.v-input--selection-controls__ripple
left: -14px

.v-input--switch .v-label
margin-left: 10px

.v-select-list.v-card
margin: 0
border-top-left-radius: 0
border-top-right-radius: 0

.v-picker.v-card
margin: 0

.theme--light.v-input:not(.v-input--is-disabled) input, .theme--light.v-input:not(.v-input--is-disabled) textarea
font-size: 14px
font-weight: 400
color: #495057

.v-select .v-select__selections
font-weight: 400
font-size: 12px
text-transform: uppercase

.v-input__control .v-counter
margin-top: 5px

.v-application code
padding: 2px 4px
font-size: 90%
font-weight: 400
color: #c7254e
background-color: #f9f2f4
border-radius: 4px
&:before, &:after
content: ""

.theme--light.v-text-field:not(.v-input--has-state) > .v-input__control > .v-input__slot:hover:before
border-color: #d2d2d2

+ 0
- 3
client.ts/src/sass/vuetify-material/_map.sass View File

@@ -1,3 +0,0 @@
.mapouter
position: relative !important
height: 100vh !important

+ 0
- 25
client.ts/src/sass/vuetify-material/_modal.sass View File

@@ -1,25 +0,0 @@
.v-dialog
.v-card
margin: 0
.v-card__title
font-weight: 300
font-size: 18px
display: inline-block
text-align: center
width: 100%
padding: 24px 24px 0
.v-icon
position: absolute
top: 15px
right: 20px
color: #999
opacity: .5
font-size: 16px
&:hover
opacity: 1

.v-dialog > .v-card > .v-card__text
padding-top: 24px
font-weight: 300
line-height: 1.75em
letter-spacing: 0

+ 0
- 12
client.ts/src/sass/vuetify-material/_notification.sass View File

@@ -1,12 +0,0 @@
.v-alert
padding: 20px 15px

.v-alert__wrapper
.v-alert__icon
height: 38px
min-width: 38px
.v-alert__content
font-weight: 300
span
font-size: 12px
font-weight: 500

+ 0
- 28
client.ts/src/sass/vuetify-material/_pages.sass View File

@@ -1,28 +0,0 @@
#pages
.v-card--plan
&.transparent
.display-2
color: #fff

#register
.v-card
.v-list-item__title
font-size: 18px
color: #3c4858
.v-list-item__subtitle
line-height: 1.5
margin-bottom: 15px

#login
.v-card
.v-card--material__heading
.v-icon
font-size: 20px

.error-page
h1.title
font-size: 192px !important
letter-spacing: 14px !important
font-weight: 700 !important
margin-top: 60px
margin-bottom: 100px

+ 0
- 5
client.ts/src/sass/vuetify-material/_pagination.sass View File

@@ -1,5 +0,0 @@
.v-pagination
.v-pagination__item,
.v-pagination__navigation
&:focus
outline: none

+ 0
- 23
client.ts/src/sass/vuetify-material/_rtl.sass View File

@@ -1,23 +0,0 @@
.v-application--is-rtl
.v-toolbar
.v-btn
.v-btn__content span
width: 20px
.v-toolbar__title
margin-right: 15px

.v-card
.v-list-item__action
.v-input--selection-controls__ripple
left: -10px

.v-timeline--align-top .v-timeline-item__body > .v-card:before
right: -11px !important
left: unset !important

#settings
.v-icon
margin-right: 30px

.v-footer
position: relative !important

+ 0
- 36
client.ts/src/sass/vuetify-material/_settings.sass View File

@@ -1,36 +0,0 @@
#settings
z-index: 200

.v-settings
border-radius: 10px
.v-card
margin-top: 0
.v-card__text
strong
height: 30px
line-height: 25px
font-size: 12px
font-weight: 600
text-transform: uppercase
text-align: center
.v-avatar
border-color: #fff
border-radius: 50% !important
cursor: pointer
display: inline-block
height: 23px
margin-right: 12px
position: relative
width: 23px
padding: 8px

.v-settings__item
border-radius: 10px
.v-image
border-radius: 7px !important

.v-settings__item:not(.v-settings__item--active)
border-color: #fff !important

.v-divider.secondary
border-color: rgb(221, 221, 221) !important

+ 0
- 79
client.ts/src/sass/vuetify-material/_sidebar.sass View File

@@ -1,79 +0,0 @@
.v-application .v-navigation-drawer .v-navigation-drawer__content .v-list-item .v-list-item__content .v-list-item__title.display-2
font-size: 18px !important
margin-top: 12px
margin-bottom: 12px

.v-application .v-navigation-drawer .v-navigation-drawer__content .v-list .v-list-group .v-list-group__header .v-list-item__content .v-list-item__title
font-size: 14px
font-weight: 300

.v-application--is-ltr .v-list-item__avatar:first-child
margin-right: 11px

.v-application .v-navigation-drawer .v-navigation-drawer__content .v-list-item__icon.v-list-group__header__append-icon .v-icon
font-size: 19px

.v-application--is-ltr #core-navigation-drawer div.v-list-item__icon--text,
.v-application--is-ltr #core-navigation-drawer div.v-list-item__icon:first-child
margin-left: 5px !important
margin-right: 18px
opacity: .8

.v-application--is-ltr .v-list-item__action:last-of-type:not(:only-child),
.v-application--is-ltr .v-list-item__avatar:last-of-type:not(:only-child),
.v-application--is-ltr .v-list-item__icon:last-of-type:not(:only-child)
margin-right: 2px

.v-list--nav.v-list--dense .v-list-item:not(:last-child):not(:only-child),
.v-list--nav .v-list-item--dense:not(:last-child):not(:only-child),
.v-list--rounded.v-list--dense .v-list-item:not(:last-child):not(:only-child),
.v-list--rounded .v-list-item--dense:not(:last-child):not(:only-child)
margin-bottom: 3px

.v-list-item .v-list-item__title, .v-list-item .v-list-item__subtitle
line-height: 1.2
font-weight: 300
font-size: 14px

.v-list-group__items .v-list-item
font-size: 13px
margin-bottom: 5px !important
.v-list-item__title
font-size: 13px
.v-list-item__icon
margin-top: 14px

.v-list-group__items .v-list-group--sub-group .v-list-group__header .v-list-item__icon--text
margin-top: 15px !important


.v-list-item__icon
margin: 12px 0

.theme--dark.v-list-item--active:hover::before, .theme--dark.v-list-item--active::before
opacity: 0

.v-navigation-drawer
.v-list-item__content
transition: all 0.3s linear 0s

.v-list--nav
padding-left: 15px
padding-right: 15px

.v-application .logo-mini
float: left
width: 30px
text-align: center
margin-left: 7px
margin-right: 15px

.theme--dark.v-navigation-drawer .v-divider
background-color: rgba(181, 181, 181, 0.2)
border-color: rgba(181, 181, 181, 0.1)
width: calc(100% - 30px)
margin-left: 15px

.v-navigation-drawer--mini-variant .v-list-item > *:not(:first-child)
display: block
opacity: 0

+ 0
- 12
client.ts/src/sass/vuetify-material/_tab.sass View File

@@ -1,12 +0,0 @@
.v-card--wizard
.v-tabs-bar
.v-slide-group__wrapper
overflow: visible
display: -webkit-inline-box
contain: inherit

.v-slide-group__content
z-index: 2

.v-tab:not(:first-child)
margin-left: 5px

+ 0
- 22
client.ts/src/sass/vuetify-material/_table.sass View File

@@ -1,22 +0,0 @@
.v-data-table td
font-weight: 300
padding: 12px 8px

.v-data-table table thead tr th
font-weight: 300
font-size: 17px
padding: 0px 8px

.v-data-table table tbody tr td .v-btn
margin-right: 0px !important

.v-data-table .v-data-table-header__sort-badge
font-size: 10px

.v-data-table.theme--dark
tr th
color: #fff !important

.theme--light
.v-data-table table thead tr th
color: #333

+ 0
- 16
client.ts/src/sass/vuetify-material/_timeline.sass View File

@@ -1,16 +0,0 @@
.v-timeline-item__dot
box-shadow: none !important

.v-timeline-item__inner-dot .v-icon
font-size: 24px

.theme--light.v-timeline::before
background-color: #e5e5e5
width: 3px

.v-timeline-item__body
.subtitle-1
font-weight: 300
font-size: 16px !important
.body-2
font-size: 12px !important

+ 0
- 4
client.ts/src/sass/vuetify-material/_vectormap.sass View File

@@ -1,4 +0,0 @@
.vue-world-map
max-height: 350px
.land
stroke: #FFF !important

+ 0
- 23
client.ts/src/sass/vuetify-material/_view.sass View File

@@ -1,23 +0,0 @@
.v-content__wrap
.container--fluid
padding-left: 30px
padding-right: 30px

.v-application .headline
font-size: 25px !important
padding-bottom: 0

.v-application .black--text
color: #333 !important

.v-application .small
font-weight: 300
line-height: 2rem
small
font-weight: 400

@media(max-width: 960px)
.v-content__wrap
.container--fluid
padding-left: 15px
padding-right: 15px

+ 0
- 38
client.ts/src/views/Index.vue View File

@@ -1,38 +0,0 @@
<template>
<v-app>
<dashboard-core-app-bar v-model="expandOnHover" />

<v-progress-linear
:active="loading"
indeterminate
absolute
color="red"
/>

<dashboard-core-drawer :expand-on-hover.sync="expandOnHover" />

<dashboard-core-view />
</v-app>
</template>

<script>
export default {
name: 'DashboardIndex',

components: {
DashboardCoreAppBar: () => import('./components/core/AppBar'),
DashboardCoreDrawer: () => import('./components/core/Drawer'),
DashboardCoreView: () => import('./components/core/View')
},

data: () => ({
expandOnHover: false
}),

computed: {
loading () {
return false // this.$store.state.loading
}
}
}
</script>

+ 0
- 183
client.ts/src/views/components/core/AppBar.vue View File

@@ -1,183 +0,0 @@
<template>
<v-app-bar
id="app-bar"
absolute
app
color="transparent"
flat
height="75"
>
<v-btn
class="mr-3"
elevation="1"
fab
small
@click="$vuetify.breakpoint.smAndDown ? setDrawer(!drawer) : $emit('input', !value)"
>
<v-icon v-if="value">
mdi-view-quilt
</v-icon>

<v-icon v-else>
mdi-dots-vertical
</v-icon>
</v-btn>

<v-toolbar-title
class="hidden-sm-and-down font-weight-light"
v-text="title"
/>

<v-spacer />

<v-btn
class="ml-2"
min-width="0"
text
href="/#/"
>
<v-icon>mdi-view-dashboard</v-icon>
</v-btn>

<v-menu
bottom
left
offset-y
origin="top right"
transition="scale-transition"
>
<template #activator="{ attrs, on }">
<v-btn
class="ml-2"
min-width="0"
text
v-bind="attrs"
v-on="on"
>
<v-badge
color="rgb(255, 4, 29)"
overlap
bordered
>
<template #badge>
<span>{{ messages.length }}</span>
</template>

<v-icon>mdi-bell</v-icon>
</v-badge>
</v-btn>
</template>

<v-list
:tile="false"
nav
>
<div>
<app-bar-item
v-for="(n, i) in messages"
:key="`item-${i}`"
>
<v-list-item-title v-text="n.message" />
</app-bar-item>
</div>
</v-list>
</v-menu>

<v-btn
v-if="!profile._id"
class="ml-2"
min-width="0"
text
to="/pages/login"
>
<v-icon>far fa-user</v-icon>
</v-btn>
<v-menu
v-else
bottom
left
min-width="200"
offset-y
origin="top right"
transition="scale-transition"
open-on-hover
>
<template #activator="{ attrs, on }">
<v-btn
class="ml-2"
min-width="0"
text
v-bind="attrs"
v-on="on"
>
<v-icon>fas fa-user</v-icon>
</v-btn>
</template>

<v-list
:tile="false"
flat
nav
>
<v-hover v-slot="{ hover }">
<v-list-item
to="/profile"
:class="{red: hover}"
>
Profil
</v-list-item>
</v-hover>
<v-divider class="mb-2 mt-2" />
<v-hover v-slot="{ hover }">
<v-list-item
:class="{red: hover}"
@click="logout"
>
Logout
</v-list-item>
</v-hover>
</v-list>
</v-menu>
</v-app-bar>
</template>

<script>
import { mapState, mapMutations, mapGetters } from 'vuex'
// import { useGraphQL } from '@/plugins/useGraphQL'

export default {
name: 'DashboardCoreAppBar',

props: {
value: {
type: Boolean,
default: false
}
},

setup (props, context) {
// return { ...useGraphQL(props, context) }
},

computed: {
...mapState(['drawer', 'messages']),
...mapGetters(['profile']),
title () {
if (this.$route.name) {
return this.$route.name
} else {
return this.$store.getters.getTitle
}
}
},

methods: {
...mapMutations({
setDrawer: 'SET_DRAWER'
}),
logout () {
// this.login('LOGOUT', 'LOGOUT')
}
}
}
</script>

+ 0
- 151
client.ts/src/views/components/core/Drawer.vue View File

@@ -1,151 +0,0 @@
<template>
<v-navigation-drawer
id="core-navigation-drawer"
v-model="drawer"
dark
:expand-on-hover="expandOnHover"
mobile-breakpoint="960"
app
mini-variant-width="80"
width="260"
v-bind="$attrs"
>
<template #img="props">
<v-img
:gradient="`150deg, rgba(192, 4, 29, .8), rgba(0, 0, 0, .8) 50%`"
v-bind="props"
/>
</template>

<v-list-item two-line>
<v-list-item-content>
<v-list-item-title class="text-uppercase font-weight-regular display-2">
<span class="logo-normal">Turnen auf Zeit</span>
</v-list-item-title>
</v-list-item-content>
</v-list-item>

<v-divider class="mb-2" />

<v-list
expand
nav
>
<!-- Style cascading bug -->
<!-- https://github.com/vuetifyjs/vuetify/pull/8574 -->
<div />

<mainmenu />

<!-- Style cascading bug -->
<!-- https://github.com/vuetifyjs/vuetify/pull/8574 -->
<div />
</v-list>
</v-navigation-drawer>
</template>

<script>
export default {
name: 'DashboardCoreDrawer',

components: {
Mainmenu: () => import('./Menu')
},

props: {
expandOnHover: {
type: Boolean,
default: false
}
},

data: () => ({
HaupteventList: [],
publicPath: process.env.BASE_URL
}),

computed: {
drawer: {
get () {
return this.$store.state.drawer
},
set (val) {
this.$store.commit('SET_DRAWER', val)
}
},
menus () {
return this.HaupteventList.slice().sort((a, b) => a.beginn > b.beginn ? -1 : 1)
}
},

watch: {
'$vuetify.breakpoint.smAndDown' (val) {
this.$emit('update:expandOnHover', !val)
}
}
}
</script>

<style lang="sass">
@import '../../../../node_modules/vuetify/src/styles/tools/rtl'

#core-navigation-drawer
&.v-navigation-drawer--mini-variant
.v-list-item
justify-content: flex-start !important

.v-list-group--sub-group
display: block !important

.v-list-group__header.v-list-item--active:before
opacity: .24

.v-list-item
&__icon--text,
&__icon:first-child
justify-content: center
text-align: center
width: 20px

+ltr()
margin-right: 24px
margin-left: 12px !important

+rtl()
margin-left: 24px
margin-right: 12px !important

.v-list--dense
.v-list-item
&__icon--text,
&__icon:first-child
margin-top: 10px

.v-list-group--sub-group
.v-list-item
+ltr()
padding-left: 8px

+rtl()
padding-right: 8px

.v-list-group__header
+ltr()
padding-right: 0

+rtl()
padding-right: 0

.v-list-item__icon--text
margin-top: 19px
order: 0

.v-list-group__header__prepend-icon
order: 2

+ltr()
margin-right: 8px

+rtl()
margin-left: 8px
</style>

+ 0
- 69
client.ts/src/views/components/core/Footer.vue View File

@@ -1,69 +0,0 @@
<template>
<v-footer
id="dashboard-core-footer"
>
<v-container>
<v-row
align="center"
no-gutters
>
<v-col
v-for="(link, i) in links"
:key="i"
class="text-center mb-sm-0 mb-5"
cols="auto"
>
<a
:href="'/#'+link.to"
class="mr-0 grey--text text--darken-3"
rel="noopener"
v-text="link.text"
/>
</v-col>

<v-spacer class="hidden-sm-and-down" />

<v-col
cols="12"
md="auto"
>
<div class="body-1 font-weight-light pt-6 pt-md-0 text-center">
&copy; 2020 IT Kimmig
</div>
</v-col>
</v-row>
</v-container>
</v-footer>
</template>

<script>
export default {
name: 'DashboardCoreFooter',

data: () => ({
links: [
{
to: '/',
text: 'Startseite'
},
{
to: '/pages/impress',
text: 'Impressum'
},
{
to: '/pages/privacy',
text: 'Datenschutz'
}
]
})
}
</script>

<style lang="sass">
#dashboard-core-footer
a
font-size: .825rem
font-weight: 500
text-decoration: none
text-transform: uppercase
</style>

+ 0
- 77
client.ts/src/views/components/core/Menu.vue View File

@@ -1,77 +0,0 @@
<template>
<div>
<base-item
:item="{
icon:'mdi-view-dashboard',
title: 'Startseite',
to: '/'
}"
/>
<base-item-group
v-if="isMaster"
:item="{
group: '/management',
icon: 'fa-user-crown',
title: 'Admin',
children: [
{
title: 'Wettkampforte',
to: 'places',
icon: 'mdi-home-group',
},
{
title: 'Hauptevents',
to: 'events',
icon: 'mdi-calendar-multiple',
},
{
title: 'Vereine',
to: 'clubs',
icon: 'mdi-account-supervisor-circle',
},
{
title: 'Disziplinen',
to: 'disciplines',
icon: 'fa-dumbbell',
},
{
title: 'Personen verwalten',
to: 'people',
icon: 'mdi-account-edit',
},
{
title: 'Personen zusammenführen',
to: 'merge',
icon: 'mdi-account-switch',
},
{
title: 'Turnportalabfrage',
to: 'turnportal',
icon: 'mdi-account-question',
},
{
title: 'Serverübersicht',
to: 'server',
icon: 'mdi-server',
},
],
}"
/>
</div>
</template>

<script>
import { mapGetters } from 'vuex'

export default {
name: 'Menu',

computed: {
...mapGetters(['profile', 'isMaster'])
}
}
</script>

<style scoped>

</style>

+ 0
- 17
client.ts/src/views/components/core/View.vue View File

@@ -1,17 +0,0 @@
<template>
<v-main>
<router-view />

<dashboard-core-footer />
</v-main>
</template>

<script>
export default {
name: 'DashboardCoreView',

components: {
DashboardCoreFooter: () => import('./Footer.vue')
}
}
</script>

+ 0
- 40
client.ts/tsconfig.json View File

@@ -1,40 +0,0 @@
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"strict": true,
"jsx": "preserve",
"importHelpers": true,
"moduleResolution": "node",
"experimentalDecorators": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"sourceMap": true,
"baseUrl": ".",
"types": [
"webpack-env"
],
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
},
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue",
"tests/**/*.ts",
"tests/**/*.tsx"
],
"exclude": [
"node_modules"
]
}

+ 0
- 5
client.ts/vue.config.js View File

@@ -1,5 +0,0 @@
module.exports = {
transpileDependencies: [
'vuetify'
]
}

+ 0
- 23
client.vue3/.gitignore View File

@@ -1,23 +0,0 @@
.DS_Store
node_modules
/dist


# local env files
.env.local
.env.*.local

# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*

# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

+ 0
- 15
client.vue3/.graphqlconfig View File

@@ -1,15 +0,0 @@
{
"name": "Untitled GraphQL Schema",
"schemaPath": "./path/to/schema.graphql",
"extensions": {
"endpoints": {
"Default GraphQL Endpoint": {
"url": "http://localhost:3000/gql",
"headers": {
"user-agent": "JS GraphQL"
},
"introspect": false
}
}
}
}

+ 0
- 24
client.vue3/README.md View File

@@ -1,24 +0,0 @@
# client

## Project setup
```
npm install
```

### Compiles and hot-reloads for development
```
npm run serve
```

### Compiles and minifies for production
```
npm run build
```

### Lints and fixes files
```
npm run lint
```

### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).

+ 0
- 5
client.vue3/babel.config.js View File

@@ -1,5 +0,0 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}

+ 0
- 28367
client.vue3/package-lock.json
File diff suppressed because it is too large
View File


+ 0
- 57
client.vue3/package.json View File

@@ -1,57 +0,0 @@
{
"name": "client",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"@fortawesome/fontawesome-pro": "^5.15.3",
"@mdi/font": "5.9.55",
"apollo-boost": "^0.4.9",
"apollo-cache-inmemory": "^1.6.6",
"core-js": "^3.12.1",
"graphql": "^15.5.0",
"js-cookie": "^2.2.1",
"roboto-fontface": "*",
"subscriptions-transport-ws": "^0.9.18",
"vue": "^3.0.0",
"vue-apollo": "^3.0.7",
"vue3-cookies": "^1.0.1",
"vuetify": "^3.0.0-alpha.0",
"vuex": "^4.0.0"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.13",
"@vue/cli-plugin-eslint": "~4.5.13",
"@vue/cli-service": "~4.5.13",
"@vue/compiler-sfc": "^3.0.11",
"babel-eslint": "^10.1.0",
"eslint": "^6.7.2",
"eslint-plugin-vue": "^7.9.0",
"sass": "^1.32.0",
"sass-loader": "^10.0.0",
"vue-cli-plugin-vuetify": "~2.4.0"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/vue3-essential",
"eslint:recommended"
],
"parserOptions": {
"parser": "babel-eslint"
},
"rules": {}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}

BIN
client.vue3/public/favicon.ico View File

Before After

+ 0
- 17
client.vue3/public/index.html View File

@@ -1,17 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<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 %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> 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>

+ 0
- 110
client.vue3/src/App.vue View File

@@ -1,110 +0,0 @@
<template>
<div>
<!--<router-view />-->
<div
v-if="!connected"
id="loader"
>
<div>
<p>Verbinde zu Server...</p>
</div>
</div>
<!--<confirm ref="confirm" />
<v-snackbar
v-model="error_visible"
class="error_snack"
:timeout="5000"
top
>
{{ error_text }}

<template #action="{ attrs }">
<v-btn
dark
text
v-bind="attrs"
@click="error_visible = false"
>
Schließen
</v-btn>
</template>
</v-snackbar>-->
Test: {{ connected ? 'connected' : 'not connected' }}
</div>
</template>

<script>
import { useGraphQL } from '@/plugins/graphql';

export default {
name: 'App',

setup () {
return {
...useGraphQL()
}
},

computed: {
},

mounted () {
},
}
</script>

<style scoped>
#loader {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
width: 100vw;
height: 100vh;
z-index: 9999;
background: rgba(255, 255, 255, 0.8);
text-align: center;
}

#loader > div {
height: 100vh;
width: 100vw;
display: table-cell;
vertical-align: middle;
text-align: center;
}

#loader > div > p {
font-family: "Roboto", sans-serif;
margin-top: 16px;
}
</style>

<style>
.changed {
color: rgb(255, 4, 29);
}

.changefade {
color: inherit;
transition: color 0.8s;
}

.trhover:hover {
cursor: pointer;
}

.center_switch > div > div > div {
margin: auto !important;
}

.error_snack .v-snack__wrapper {
font-family: "Roboto", sans-serif;
background-color: red !important;
}

.row-clickable tbody tr:hover {
cursor: pointer;
}
</style>

BIN
client.vue3/src/assets/favicon.ico View File

Before After

BIN
client.vue3/src/assets/logo.png View File

Before After
Width: 200  |  Height: 200  |  Size: 6.7KB

+ 0
- 1
client.vue3/src/assets/logo.svg 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>

+ 0
- 107
client.vue3/src/components/Confirm.vue View File

@@ -1,107 +0,0 @@
<template>
<v-dialog
v-model="dialog"
:max-width="options.width"
:style="{ zIndex: options.zIndex }"
@keydown.esc="cancel"
>
<v-card>
<v-toolbar
dark
:color="options.color"
dense
flat
>
<v-toolbar-title class="white--text">
{{ title }}
</v-toolbar-title>
</v-toolbar>
<v-card-text
v-show="!!message"
class="pa-4"
>
{{ message }}
</v-card-text>
<v-card-actions class="pt-0">
<v-spacer />
<v-btn
color="primary darken-1"
@click.native="agree"
>
Ja
</v-btn>
<v-btn
color="grey"
@click.native="cancel"
>
Abbrechen
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>

<script>
/**
* Vuetify Confirm Dialog component
*
* Insert component where you want to use it:
* <confirm ref="confirm"></confirm>
*
* Call it:
* this.$refs.confirm.open('Delete', 'Are you sure?', { color: 'red' }).then((confirm) => {})
* Or use await:
* if (await this.$refs.confirm.open('Delete', 'Are you sure?', { color: 'red' })) {
* // yes
* }
* else {
* // cancel
* }
*
* Alternatively you can place it in main App component and access it globally via this.$root.$confirm
* <template>
* <v-app>
* ...
* <confirm ref="confirm"></confirm>
* </v-app>
* </template>
*
* mounted() {
* this.$root.$confirm = this.$refs.confirm.open
* }
*/
export default {
data: () => ({
dialog: false,
resolve: null,
reject: null,
message: null,
title: null,
options: {
color: 'primary',
width: 290,
zIndex: 200,
},
}),
methods: {
open (title, message, options) {
this.dialog = true
this.title = title
this.message = message
this.options = Object.assign(this.options, options)
return new Promise((resolve, reject) => {
this.resolve = resolve
this.reject = reject
})
},
agree () {
this.resolve(true)
this.dialog = false
},
cancel () {
this.resolve(false)
this.dialog = false
},
},
}
</script>

+ 0
- 90
client.vue3/src/components/DateSelector.vue View File

@@ -1,90 +0,0 @@
<template>
<v-menu
v-model="show"
:close-on-content-click="false"
:nudge-right="40"
transition="scale-transition"
offset-y
min-width="290px"
>
<template #activator="{ on }">
<v-text-field
v-model="dateFormatted"
:label="label"
:clearable="clearable"
prepend-icon="mdi-calendar"
readonly
v-on="on"
/>
</template>
<v-date-picker
v-model="date"
:first-day-of-week="1"
@input="show = false"
/>
</v-menu>
</template>

<script>
export default {
name: 'DateSelector',

props: {
value: {
type: String,
default: '',
},
label: {
type: String,
default: '',
},
clearable: {
type: Boolean,
required: false,
default: false,
},
},

data: () => ({
show: false,
}),

computed: {
dateFormatted: {
get () {
return this.formatDate(this.value)
},
set (val) {
this.$emit('input', this.parseDate(val))
},
},
date: {
get () {
return this.value
},
set (val) {
this.$emit('input', val)
},
},
},

methods: {
formatDate (date) {
if (!date) return null

const [year, month, day] = date.split('-')
return `${day}.${month}.${year}`
},
parseDate (date) {
if (!date) return null

const [day, month, year] = date.split('/')
return `${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')}`
},
},
}
</script>

<style scoped>

</style>

+ 0
- 139
client.vue3/src/components/DateTimeSelector.vue View File

@@ -1,139 +0,0 @@
<template>
<v-row>
<v-col cols="6">
<v-menu
v-model="showdate"
:close-on-content-click="false"
:nudge-right="40"
transition="scale-transition"
offset-y
min-width="290px"
>
<template #activator="{ on }">
<v-text-field
:value="dateFormatted"
:label="`${label}-Datum`"
:disabled="disabled"
prepend-icon="far fa-calendar-day"
readonly
v-on="on"
/>
</template>
<v-date-picker
v-model="date"
:first-day-of-week="1"
@input="showdate = false"
/>
</v-menu>
</v-col>
<v-col cols="6">
<v-menu
v-model="showtime"
:close-on-content-click="false"
:nudge-right="40"
transition="scale-transition"
offset-y
min-width="290px"
>
<template #activator="{ on }">
<v-text-field
:value="timeFormatted"
:label="`${label}-Uhrzeit`"
:disabled="disabled"
prepend-icon="far fa-clock"
readonly
v-on="on"
/>
</template>
<v-time-picker
v-model="time"
format="24hr"
:allowed-minutes="m => m % 5 === 0"
@input="showtime = false"
/>
</v-menu>
</v-col>
</v-row>
</template>

<script>
export default {
name: 'DateSelector',

props: {
value: {
type: String,
default: '',
},
label: {
type: String,
default: '',
},
disabled: {
type: Boolean,
default: false,
},
},

data: () => ({
showdate: false,
showtime: false,
}),

computed: {
_date () {
return (this.value?.split(' ') || [])[0] || ''
},
_time () {
return (this.value?.split(' ') || [])[1] || ''
},
dateFormatted () {
return this.formatDate(this._date)
},
timeFormatted () {
return this.formatTime(this._time)
},
date: {
get () {
return this._date
},
set (val) {
this.$emit('input', `${val} ${this._time}`)
},
},
time: {
get () {
return this._time
},
set (val) {
this.$emit('input', `${this._date} ${val}:00`)
},
},
},

methods: {
formatDate (date) {
if (!date) return null

const [year, month, day] = date.split('-')
return `${day}.${month}.${year}`
},
formatTime (time) {
if (!time) return null

const [hour, minute] = time.split(':')
return `${hour}:${minute}`
},
parseDate (date) {
if (!date) return null

const [day, month, year] = date.split('/')
return `${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')}`
},
},
}
</script>

<style scoped>

</style>

+ 0
- 20
client.vue3/src/components/Demo.vue View File

@@ -1,20 +0,0 @@
<template>
<div>
T2: {{ get('pub_person','b77038e7-9f17-4ef2-951c-958aeba8a5b5') }}
</div>
</template>

<script>
import { getData } from '../plugins/useSDB'

export default {
name: 'Demo',
setup () {
return { ...getData() }
},
}
</script>

<style scoped>

</style>

+ 0
- 9
client.vue3/src/components/base/Card.vue View File

@@ -1,9 +0,0 @@
<script>
import { VCard } from 'vuetify/lib'

export default {
name: 'Card',

extends: VCard,
}
</script>

+ 0
- 77
client.vue3/src/components/base/EditDialog.vue View File

@@ -1,77 +0,0 @@
<template>
<v-edit-dialog
v-model:return-value="wert"
large
persistent
:save-text="savebutton"
:cancel-text="cancelbutton"
@open="open"
@save="save"
@close="close"
>
<div style="min-width:40px;">
{{ value }}
</div>
<template #input>
<v-text-field
v-model="wert"
:label="label"
autofocus
/>
</template>
</v-edit-dialog>
</template>

<script>
export default {
name: 'EditDialog',

props: {
value: {
type: [String, Number],
required: false,
default: '',
},
label: {
type: String,
required: false,
default: '',
},
savebutton: {
type: String,
required: false,
default: 'Übernehmen',
},
cancelbutton: {
type: String,
required: false,
default: 'Abbrechen',
},
},

data: () => ({
wert: null,
}),

methods: {
open () {
if (this.value === null || this.value === undefined) {
this.wert = ''
} else {
this.wert = `${this.value}`
}
},
close () {
this.wert = null
},
save () {
this.$emit('input', this.wert)
this.wert = null
},
},
}
</script>

<style scoped>

</style>

+ 0
- 76
client.vue3/src/components/base/EditDialogDate.vue View File

@@ -1,76 +0,0 @@
<template>
<v-edit-dialog
v-model:return-value="wert"
large
persistent
:save-text="savebutton"
cancel-text="Abbrechen"
@open="open"
@save="save"
@close="close"
>
<div>{{ value | dateformat(dateformat) }}</div>
<template #input>
<date-selector
v-model="wert"
:label="label"
:clearable="clearable"
/>
</template>
</v-edit-dialog>
</template>

<script>
export default {
name: 'EditDialog',

props: {
value: {
type: String,
required: false,
default: undefined,
},
label: {
type: String,
required: false,
default: '',
},
savebutton: {
type: String,
required: false,
default: 'Übernehmen',
},
dateformat: {
type: String,
required: false,
default: null,
},
clearable: {
type: Boolean,
required: false,
default: false,
},
},

data: () => ({
wert: null,
}),

methods: {
open () {
this.wert = this.value
},
close () {
this.wert = null
},
save () {
this.$emit('input', this.wert)
this.wert = null
},
},
}
</script>

<style scoped>

</style>

+ 0
- 85
client.vue3/src/components/base/EditDialogSelect.vue View File

@@ -1,85 +0,0 @@
<template>
<v-edit-dialog
v-model:return-value="wert"
large
persistent
:save-text="savebutton"
cancel-text="Abbrechen"
@open="open"
@save="save"
@close="close"
>
<div>{{ text }}</div>
<template #input>
<v-select
v-model="wert"
:label="label"
:items="myitems"
autofocus
clearable
/>
</template>
</v-edit-dialog>
</template>

<script>
import { enumtype } from '@/enumtype'

export default {
name: 'EditDialogSelect',

props: {
value: {
type: String,
required: false,
default: undefined,
},
label: {
type: String,
required: false,
default: '',
},
savebutton: {
type: String,
required: false,
default: 'Übernehmen',
},
items: {
type: undefined,
required: true,
},
},

data: () => ({
wert: null,
}),

computed: {
text () {
return this.myitems?.find(e => e.value === this.value)?.text
},
myitems () {
if (Array.isArray(this.items)) return this.items
else if (typeof enumtype[this.items] === 'function') return enumtype[this.items]()
return []
},
},

methods: {
open () {
this.wert = this.value
},
close () {
this.wert = null
},
save () {
this.$emit('input', this.wert)
this.wert = null
},
},
}
</script>

<style scoped>

</style>

+ 0
- 69
client.vue3/src/components/base/Item.vue View File

@@ -1,69 +0,0 @@
<template>
<v-list-item
:href="href"
:rel="href && href !== '#' ? 'noopener' : undefined"
:target="href && href !== '#' ? '_blank' : undefined"
:to="item.to"
:active-class="`primary ${!isDark ? 'black' : 'white'}--text`"
>
<v-list-item-icon v-if="item.icon">
<v-icon v-text="item.icon" />
</v-list-item-icon>

<v-list-item-icon
v-else-if="text"
class="v-list-item__icon--text"
v-text="computedText"
/>

<v-list-item-content v-if="item.title || item.subtitle">
<v-list-item-title v-text="item.title" />

<v-list-item-subtitle v-text="item.subtitle" />
</v-list-item-content>
</v-list-item>
</template>

<script>
import Themeable from 'vuetify/lib/mixins/themeable'

export default {
name: 'Item',

mixins: [Themeable],

props: {
item: {
type: Object,
default: () => ({
href: undefined,
icon: undefined,
subtitle: undefined,
title: undefined,
to: undefined,
}),
},
text: {
type: Boolean,
default: false,
},
},

computed: {
computedText () {
if (!this.item || !this.item.title) return ''

let text = ''

this.item.title.split(' ').forEach(val => {
text += val.substring(0, 1)
})

return text
},
href () {
return this.item.href || (!this.item.to ? '#' : undefined)
},
},
}
</script>

+ 0
- 142
client.vue3/src/components/base/ItemGroup.vue View File

@@ -1,142 +0,0 @@
<template>
<v-list-group
:group="group"
:prepend-icon="item.icon"
:sub-group="subGroup"
append-icon="mdi-menu-down"
:color="barColor !== 'rgba(255, 255, 255, 1), rgba(255, 255, 255, 0.7)' ? 'white' : 'grey darken-1'"
>
<template #activator>
<v-list-item-icon
v-if="text"
class="v-list-item__icon--text"
v-text="computedText"
/>

<v-list-item-avatar
v-else-if="item.avatar"
class="align-self-center"
color="grey"
>
<v-img src="https://demos.creative-tim.com/material-dashboard-pro/assets/img/faces/avatar.jpg" />
</v-list-item-avatar>

<v-list-item-content>
<v-list-item-title v-text="item.title" />
<v-tooltip bottom>
<template #activator="{ on }">
<v-list-item-subtitle
v-on="on"
v-text="item.subtitle"
/>
</template>
<span>{{ item.subtitle }}</span>
</v-tooltip>
</v-list-item-content>
</template>

<template v-for="(child, i) in children">
<base-item-sub-group
v-if="child.children"
:key="`sub-group-${i}`"
:item="buildChild(child)"
/>

<base-item
v-else
:key="`item-${i}`"
:item="child"
text
/>
</template>
</v-list-group>
</template>

<script>
// Utilities
import kebabCase from 'lodash/kebabCase'
import { mapState } from 'vuex'

export default {
name: 'ItemGroup',

inheritAttrs: false,

props: {
item: {
type: Object,
default: () => ({
avatar: undefined,
group: undefined,
title: undefined,
children: [],
}),
},
subGroup: {
type: Boolean,
default: false,
},
text: {
type: Boolean,
default: false,
},
},

computed: {
...mapState(['barColor']),
children () {
return this.item.children.map(item => ({
...item,
to: !item.to ? undefined : `${this.item.group}/${item.to}`,
}))
},
computedText () {
if (!this.item || !this.item.title) return ''

let text = ''

this.item.title.split(' ').forEach(val => {
text += val.substring(0, 1)
})

return text
},
group () {
return this.genGroup(this.item.children)
},
},

methods: {
genGroup (children, parentgroup) {
if (!parentgroup) {
parentgroup = this.item.group
}
return children
.filter(item => item.to || item.group)
.map(item => {
let group = `${parentgroup}/${kebabCase(item.to)}`

if (item.children) {
group = `${group}|${this.genGroup(item.children, `${parentgroup}/${item.group}`)}`
}

return group
}).join('|')
},
buildChild (child) {
return { ...child, group: `${this.item.group}/${child.group}` }
},
},
}
</script>

<style>
.v-list-group__activator p {
margin-bottom: 0;
}

.v-list-group .v-list-group__items {
padding-left: 3px;
border-left: 1px dashed red;
}
</style>

+ 0
- 0
client.vue3/src/components/base/ItemSubGroup.vue View File


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

Loading…
Cancel
Save