> 1% | |||||
last 2 versions | |||||
not dead |
[*.{js,jsx,ts,tsx,vue}] | |||||
indent_style = space | |||||
indent_size = 2 | |||||
trim_trailing_whitespace = true | |||||
insert_final_newline = true |
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" | |||||
} | |||||
} |
.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? |
# 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/). |
module.exports = { | |||||
presets: [ | |||||
'@vue/cli-plugin-babel/preset' | |||||
] | |||||
} |
{ | |||||
"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" | |||||
} | |||||
} |
<!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> |
<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> |
<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> |
<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> |
<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> |
<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> |
<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> |
<script> | |||||
import { VCard } from 'vuetify/lib' | |||||
export default { | |||||
name: 'Card', | |||||
extends: VCard | |||||
} | |||||
</script> |
<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> |
<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> |
<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> |
<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> |
<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> |
<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> |
<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> |
<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> |
<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> |
<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> |
<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> |
<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> |
<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> |
<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> |
<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> |
<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> |
<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> |
<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> |
<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> |
<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> |
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') |
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) | |||||
}) |
import Vue from 'vue' | |||||
import VueCompositionAPI from '@vue/composition-api' | |||||
Vue.use(VueCompositionAPI) |
import Vue from 'vue' | |||||
import VueCookies from 'vue-cookies' | |||||
Vue.use(VueCookies) |
// @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 } | |||||
} |
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 |
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 | |||||
} | |||||
}) |
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 | |||||
} | |||||
} | |||||
}) |
// ========================================================= | |||||
// * 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 |
$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 ) | |||||
); |
#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 |
.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 |
#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 |
.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 |
.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 |
.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 |
.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 |
.mapouter | |||||
position: relative !important | |||||
height: 100vh !important |
.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 |
.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 |
#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 |
.v-pagination | |||||
.v-pagination__item, | |||||
.v-pagination__navigation | |||||
&:focus | |||||
outline: none |
.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 |
#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 |
.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 |
.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 |
.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 |
.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 |
.vue-world-map | |||||
max-height: 350px | |||||
.land | |||||
stroke: #FFF !important |
.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 |
<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> |
<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> |
<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> |
<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"> | |||||
© 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> |
<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> |
<template> | |||||
<v-main> | |||||
<router-view /> | |||||
<dashboard-core-footer /> | |||||
</v-main> | |||||
</template> | |||||
<script> | |||||
export default { | |||||
name: 'DashboardCoreView', | |||||
components: { | |||||
DashboardCoreFooter: () => import('./Footer.vue') | |||||
} | |||||
} | |||||
</script> |
{ | |||||
"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" | |||||
] | |||||
} |
module.exports = { | |||||
transpileDependencies: [ | |||||
'vuetify' | |||||
] | |||||
} |
.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? |
{ | |||||
"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 | |||||
} | |||||
} | |||||
} | |||||
} |
# 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/). |
module.exports = { | |||||
presets: [ | |||||
'@vue/cli-plugin-babel/preset' | |||||
] | |||||
} |
{ | |||||
"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" | |||||
] | |||||
} |
<!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> |
<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> |
<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> |
<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> |
<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> |
<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> |
<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> |
<script> | |||||
import { VCard } from 'vuetify/lib' | |||||
export default { | |||||
name: 'Card', | |||||
extends: VCard, | |||||
} | |||||
</script> |
<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> |
<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> |
<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> |
<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> |
<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> |