Merge remote-tracking branch 'origin/develop' into feature/pricelist
This commit is contained in:
commit
741216ac3e
|
@ -0,0 +1,21 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright 2021 Tim Gröger | Flaschengeist Developers
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||||
|
of the Software, and to permit persons to whom the Software is furnished to do
|
||||||
|
so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
75
README.md
75
README.md
|
@ -1,47 +1,62 @@
|
||||||
# Flaschengeist (flaschengeist-frontend)
|
# Flaschengeist (frontend)
|
||||||
|
|
||||||
Dynamischen Managementsystem für Studentenclubs
|
Modular student club administration system, licensed under the MIT license.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### Install the dependencies
|
||||||
|
|
||||||
## Install the dependencies
|
|
||||||
```bash
|
```bash
|
||||||
yarn install
|
yarn install
|
||||||
```
|
```
|
||||||
|
|
||||||
## Plugins
|
### Configure Plugins
|
||||||
### Build a Plugin
|
|
||||||
|
You can activate and deactive Plugins in `src/boot/plugins.ts`.
|
||||||
|
You have to set the name of the Plugin into `config.loadModules`.
|
||||||
|
|
||||||
|
### Build the application
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn quasar build
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
### Icons used
|
||||||
|
|
||||||
|
We are using the `mdi-v5` icon set, so feel free to use any icon from it.
|
||||||
|
A list can be found [here](https://materialdesignicons.com/)
|
||||||
|
|
||||||
|
### Commands useful for development
|
||||||
|
|
||||||
|
#### Start the app in development mode
|
||||||
|
|
||||||
|
Provides hot-code reloading, error reporting, etc.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn quasar dev
|
||||||
|
```
|
||||||
|
|
||||||
|
#### File linting
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn run lint
|
||||||
|
```
|
||||||
|
|
||||||
|
### Plugins
|
||||||
|
|
||||||
|
#### Build a Plugin
|
||||||
|
|
||||||
A Flaschengeist-Frontend-Plugin should be placed in `src/plugins`.
|
A Flaschengeist-Frontend-Plugin should be placed in `src/plugins`.
|
||||||
It needs a `plugin.ts` File which exports a plugin with the following interface:
|
It needs a `plugin.ts` File which exports a plugin with the following interface:
|
||||||
|
|
||||||
```
|
```
|
||||||
name: string;
|
name: string;
|
||||||
mainRoutes?: PluginRouteConfig[];
|
mainRoutes?: PluginRouteConfig[];
|
||||||
outRoutes?: PluginRouteConfig[];
|
outRoutes?: PluginRouteConfig[];
|
||||||
store?: Map<string, Module<any, StateInterface>>;
|
|
||||||
requiredModules: string[];
|
requiredModules: string[];
|
||||||
version: string;
|
version: string;
|
||||||
```
|
```
|
||||||
|
|
||||||
You have to import `FG_Plugin` from `plugins.d.ts`.
|
You have to import `FG_Plugin` from `plugins.d.ts`.
|
||||||
|
|
||||||
### Configure Plugin
|
|
||||||
|
|
||||||
You can activate and deactive Plugins in `src/boot/plugins.ts`. You have to set the name of the Plugin into `config.loadModules`.
|
|
||||||
The order of the plugins is importend!
|
|
||||||
|
|
||||||
### Start the app in development mode (hot-code reloading, error reporting, etc.)
|
|
||||||
```bash
|
|
||||||
yarn quasar dev
|
|
||||||
```
|
|
||||||
|
|
||||||
### Lint the files
|
|
||||||
```bash
|
|
||||||
yarn run lint
|
|
||||||
```
|
|
||||||
|
|
||||||
### Build the app for production
|
|
||||||
```bash
|
|
||||||
yarn quasar build
|
|
||||||
```
|
|
||||||
<!--
|
|
||||||
### Customize the configuration
|
|
||||||
See [Configuring quasar.conf.js](https://quasar.dev/quasar-cli/quasar-conf-js).
|
|
||||||
-->
|
|
||||||
|
|
50
package.json
50
package.json
|
@ -1,10 +1,15 @@
|
||||||
{
|
{
|
||||||
"name": "flaschengeist-frontend",
|
|
||||||
"version": "0.1.0-alpha.1",
|
|
||||||
"description": "Dynamischen Managementsystem für Studentenclubs",
|
|
||||||
"productName": "Flaschengeist",
|
|
||||||
"author": "Tim Gröger <tim@groeger-clan.de>",
|
|
||||||
"private": true,
|
"private": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"version": "2.0.0-alpha.1",
|
||||||
|
"productName": "Flaschengeist",
|
||||||
|
"name": "flaschengeist-frontend",
|
||||||
|
"author": "Tim Gröger <flaschengeist@wu5.de>",
|
||||||
|
"homepage": "https://flaschengeist.dev/Flaschengeist",
|
||||||
|
"description": "Modular student club administration system",
|
||||||
|
"bugs": {
|
||||||
|
"url" : "https://flaschengeist.dev/Flaschengeist/flaschengeist-frontend/issues"
|
||||||
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"format": "prettier --config ./package.json --write '{,!(node_modules)/**/}*.ts'",
|
"format": "prettier --config ./package.json --write '{,!(node_modules)/**/}*.ts'",
|
||||||
"lint": "eslint --ext .js,.ts,.vue ./src"
|
"lint": "eslint --ext .js,.ts,.vue ./src"
|
||||||
|
@ -12,32 +17,31 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^0.21.1",
|
"axios": "^0.21.1",
|
||||||
"cordova": "^10.0.0",
|
"cordova": "^10.0.0",
|
||||||
"core-js": "^3.9.1",
|
"pinia": "^2.0.0-alpha.10",
|
||||||
"pinia": "^2.0.0-alpha.7",
|
|
||||||
"quasar": "^2.0.0-beta.11"
|
"quasar": "^2.0.0-beta.11"
|
||||||
},
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@quasar/app": "^3.0.0-beta.10",
|
||||||
|
"@quasar/extras": "^1.10.2",
|
||||||
|
"@quasar/quasar-app-extension-qcalendar": "file:deps/quasar-ui-qcalendar/app-extension",
|
||||||
|
"@types/node": "^12.20.7",
|
||||||
|
"@types/webpack": "^4.41.27",
|
||||||
|
"@types/webpack-env": "^1.16.0",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^4.20.0",
|
||||||
|
"@typescript-eslint/parser": "^4.20.0",
|
||||||
|
"eslint": "^7.23.0",
|
||||||
|
"eslint-config-prettier": "^8.1.0",
|
||||||
|
"eslint-plugin-vue": "^7.8.0",
|
||||||
|
"eslint-webpack-plugin": "^2.5.3",
|
||||||
|
"prettier": "^2.2.1",
|
||||||
|
"typescript": "^4.2.3"
|
||||||
|
},
|
||||||
"prettier": {
|
"prettier": {
|
||||||
"singleQuote": true,
|
"singleQuote": true,
|
||||||
"semi": true,
|
"semi": true,
|
||||||
"printWidth": 100,
|
"printWidth": 100,
|
||||||
"arrowParens": "always"
|
"arrowParens": "always"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
|
||||||
"@quasar/app": "^3.0.0-beta.10",
|
|
||||||
"@quasar/extras": "^1.10.0",
|
|
||||||
"@quasar/quasar-app-extension-qcalendar": "file:deps/quasar-ui-qcalendar/app-extension",
|
|
||||||
"@types/node": "^12.20.6",
|
|
||||||
"@types/webpack": "^4.41.26",
|
|
||||||
"@types/webpack-env": "^1.16.0",
|
|
||||||
"@typescript-eslint/eslint-plugin": "^4.18.0",
|
|
||||||
"@typescript-eslint/parser": "^4.18.0",
|
|
||||||
"eslint": "^7.22.0",
|
|
||||||
"eslint-config-prettier": "^8.1.0",
|
|
||||||
"eslint-plugin-vue": "^7.7.0",
|
|
||||||
"eslint-webpack-plugin": "^2.5.2",
|
|
||||||
"prettier": "^2.2.1",
|
|
||||||
"typescript": "^4.2.3"
|
|
||||||
},
|
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
"last 10 Chrome versions",
|
"last 10 Chrome versions",
|
||||||
"last 10 Firefox versions",
|
"last 10 Firefox versions",
|
||||||
|
|
|
@ -37,16 +37,16 @@ module.exports = configure(function (/* ctx */) {
|
||||||
|
|
||||||
// https://github.com/quasarframework/quasar/tree/dev/extras
|
// https://github.com/quasarframework/quasar/tree/dev/extras
|
||||||
extras: [
|
extras: [
|
||||||
// 'ionicons-v4',
|
|
||||||
'mdi-v5',
|
|
||||||
// 'fontawesome-v5',
|
|
||||||
// 'eva-icons',
|
// 'eva-icons',
|
||||||
// 'themify',
|
// 'fontawesome-v5',
|
||||||
|
// 'ionicons-v4',
|
||||||
// 'line-awesome',
|
// 'line-awesome',
|
||||||
|
// 'material-icons',
|
||||||
|
'mdi-v5',
|
||||||
|
// 'themify',
|
||||||
|
|
||||||
// 'roboto-font-latin-ext', // this or either 'roboto-font', NEVER both!
|
// 'roboto-font-latin-ext', // this or either 'roboto-font', NEVER both!
|
||||||
|
|
||||||
'roboto-font', // optional, you are not bound to it
|
'roboto-font', // optional, you are not bound to it
|
||||||
'material-icons', // optional, you are not bound to it
|
|
||||||
],
|
],
|
||||||
|
|
||||||
// Full list of options: https://quasar.dev/quasar-cli/quasar-conf-js#Property%3A-build
|
// Full list of options: https://quasar.dev/quasar-cli/quasar-conf-js#Property%3A-build
|
||||||
|
@ -74,7 +74,7 @@ module.exports = configure(function (/* ctx */) {
|
||||||
chainWebpack (chain) {
|
chainWebpack (chain) {
|
||||||
chain.plugin('eslint-webpack-plugin')
|
chain.plugin('eslint-webpack-plugin')
|
||||||
.use(ESLintPlugin, [{
|
.use(ESLintPlugin, [{
|
||||||
extensions: [ 'js', 'vue' ],
|
extensions: [ 'ts', 'js', 'vue' ],
|
||||||
exclude: 'node_modules'
|
exclude: 'node_modules'
|
||||||
}])
|
}])
|
||||||
},
|
},
|
||||||
|
@ -90,7 +90,7 @@ module.exports = configure(function (/* ctx */) {
|
||||||
|
|
||||||
// https://quasar.dev/quasar-cli/quasar-conf-js#Property%3A-framework
|
// https://quasar.dev/quasar-cli/quasar-conf-js#Property%3A-framework
|
||||||
framework: {
|
framework: {
|
||||||
iconSet: 'material-icons', // Quasar icon set
|
iconSet: 'mdi-v5', // Quasar icon set
|
||||||
lang: 'de', // Quasar language pack
|
lang: 'de', // Quasar language pack
|
||||||
config: {
|
config: {
|
||||||
dark: 'auto',
|
dark: 'auto',
|
||||||
|
@ -134,7 +134,7 @@ module.exports = configure(function (/* ctx */) {
|
||||||
manifest: {
|
manifest: {
|
||||||
name: 'Flaschengeist',
|
name: 'Flaschengeist',
|
||||||
short_name: 'Flaschengeist',
|
short_name: 'Flaschengeist',
|
||||||
description: 'Dynamischen Managementsystem für Studentenclubs',
|
description: 'Modular student club administration system',
|
||||||
display: 'standalone',
|
display: 'standalone',
|
||||||
orientation: 'portrait',
|
orientation: 'portrait',
|
||||||
background_color: '#ffffff',
|
background_color: '#ffffff',
|
||||||
|
|
|
@ -7,10 +7,11 @@
|
||||||
:placeholder="placeholder"
|
:placeholder="placeholder"
|
||||||
:rules="customRules"
|
:rules="customRules"
|
||||||
:clearable="clearable"
|
:clearable="clearable"
|
||||||
|
v-bind="attrs"
|
||||||
@clear="dateTime = ''"
|
@clear="dateTime = ''"
|
||||||
>
|
>
|
||||||
<template #append>
|
<template #append>
|
||||||
<q-icon v-if="'date' || type == 'datetime'" name="event" class="cursor-pointer">
|
<q-icon v-if="'date' || type == 'datetime'" name="mdi-calendar" class="cursor-pointer">
|
||||||
<q-popup-proxy ref="qDateProxy" transition-show="scale" transition-hide="scale">
|
<q-popup-proxy ref="qDateProxy" transition-show="scale" transition-hide="scale">
|
||||||
<q-date v-model="date" mask="YYYY-MM-DD">
|
<q-date v-model="date" mask="YYYY-MM-DD">
|
||||||
<div class="row items-center justify-end">
|
<div class="row items-center justify-end">
|
||||||
|
@ -58,7 +59,7 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
emits: { 'update:modelValue': (date?: Date) => !!date || !date },
|
emits: { 'update:modelValue': (date?: Date) => !!date || !date },
|
||||||
setup(props, { emit }) {
|
setup(props, { emit, attrs }) {
|
||||||
const customRules = computed(() => [
|
const customRules = computed(() => [
|
||||||
props.type == 'date' ? stringIsDate : props.type == 'time' ? stringIsTime : stringIsDateTime,
|
props.type == 'date' ? stringIsDate : props.type == 'time' ? stringIsTime : stringIsDateTime,
|
||||||
...props.rules,
|
...props.rules,
|
||||||
|
@ -138,12 +139,13 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
attrs,
|
||||||
clearable,
|
clearable,
|
||||||
date,
|
|
||||||
time,
|
|
||||||
dateTime,
|
|
||||||
customRules,
|
customRules,
|
||||||
|
date,
|
||||||
|
dateTime,
|
||||||
placeholder,
|
placeholder,
|
||||||
|
time,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
<template>
|
||||||
|
<q-input v-model="password" v-bind="attrs" :label="label" :type="type">
|
||||||
|
<template #append><q-icon :name="name" class="cursor-pointer" @click="toggle" /></template
|
||||||
|
></q-input>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { computed, defineComponent, ref } from 'vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'PasswordInput',
|
||||||
|
props: {
|
||||||
|
modelValue: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
emits: {
|
||||||
|
'update:modelValue': (value: string) => !!value,
|
||||||
|
},
|
||||||
|
setup(props, { emit, attrs }) {
|
||||||
|
const isPassword = ref(true);
|
||||||
|
const type = computed(() => (isPassword.value ? 'password' : 'text'));
|
||||||
|
const name = computed(() => (isPassword.value ? 'mdi-eye-off' : 'mdi-eye'));
|
||||||
|
const password = computed({
|
||||||
|
get: () => props.modelValue,
|
||||||
|
set: (value: string) => emit('update:modelValue', value),
|
||||||
|
});
|
||||||
|
function toggle() {
|
||||||
|
isPassword.value = !isPassword.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
attrs,
|
||||||
|
isPassword,
|
||||||
|
name,
|
||||||
|
password,
|
||||||
|
toggle,
|
||||||
|
type,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -1,15 +1,8 @@
|
||||||
<template>
|
<template>
|
||||||
<q-layout view="hHh lpr lFf">
|
<q-layout view="hHh Lpr lff">
|
||||||
<q-header elevated class="bg-primary text-white">
|
<q-header elevated class="bg-primary text-white">
|
||||||
<q-toolbar>
|
<q-toolbar>
|
||||||
<q-btn
|
<q-btn dense flat round icon="mdi-menu" @click="openMenu" />
|
||||||
v-if="!leftDrawerOpen"
|
|
||||||
dense
|
|
||||||
flat
|
|
||||||
round
|
|
||||||
icon="menu"
|
|
||||||
@click="leftDrawerOpen = !leftDrawerOpen"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<q-toolbar-title>
|
<q-toolbar-title>
|
||||||
<router-link :to="{ name: 'dashboard' }" style="text-decoration: none; color: inherit">
|
<router-link :to="{ name: 'dashboard' }" style="text-decoration: none; color: inherit">
|
||||||
|
@ -21,7 +14,7 @@
|
||||||
</q-toolbar-title>
|
</q-toolbar-title>
|
||||||
|
|
||||||
<!-- Hier kommen die Shortlinks hin -->
|
<!-- Hier kommen die Shortlinks hin -->
|
||||||
<q-btn icon="message" flat dense
|
<q-btn icon="mdi-message-bulleted" flat dense
|
||||||
><q-badge color="negative" floating>{{ notifications.length }}</q-badge>
|
><q-badge color="negative" floating>{{ notifications.length }}</q-badge>
|
||||||
<q-menu style="max-height: 400px; overflow: auto">
|
<q-menu style="max-height: 400px; overflow: auto">
|
||||||
<q-btn
|
<q-btn
|
||||||
|
@ -50,12 +43,11 @@
|
||||||
</q-header>
|
</q-header>
|
||||||
|
|
||||||
<q-drawer
|
<q-drawer
|
||||||
v-model="leftDrawerOpen"
|
v-model="leftDrawer"
|
||||||
show-if-above
|
|
||||||
side="left"
|
side="left"
|
||||||
bordered
|
bordered
|
||||||
:mini="leftDrawerMini"
|
:mini="leftDrawerMini"
|
||||||
@click.capture="leftDrawerClicker"
|
@click.capture="openMenu"
|
||||||
>
|
>
|
||||||
<!-- Plugins -->
|
<!-- Plugins -->
|
||||||
<q-list>
|
<q-list>
|
||||||
|
@ -66,28 +58,13 @@
|
||||||
/>
|
/>
|
||||||
<q-separator />
|
<q-separator />
|
||||||
<!-- Plugin functions -->
|
<!-- Plugin functions -->
|
||||||
|
|
||||||
<essential-link
|
<essential-link
|
||||||
v-for="(entry, index) in subLinks"
|
v-for="(entry, index) in subLinks"
|
||||||
:key="'childPlugin' + index"
|
:key="'childPlugin' + index"
|
||||||
:entry="entry"
|
:entry="entry"
|
||||||
/>
|
/>
|
||||||
</q-list>
|
</q-list>
|
||||||
|
|
||||||
<div class="q-mini-drawer-hide absolute" style="top: 15px; right: -11px">
|
|
||||||
<q-btn
|
|
||||||
size="sm"
|
|
||||||
dense
|
|
||||||
round
|
|
||||||
unelevated
|
|
||||||
color="secondary"
|
|
||||||
icon="chevron_left"
|
|
||||||
@click="leftDrawerMini = true"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<q-separator />
|
<q-separator />
|
||||||
|
|
||||||
<essential-link
|
<essential-link
|
||||||
v-for="(entry, index) in essentials"
|
v-for="(entry, index) in essentials"
|
||||||
:key="'essential' + index"
|
:key="'essential' + index"
|
||||||
|
@ -126,7 +103,7 @@ export default defineComponent({
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const mainStore = useMainStore();
|
const mainStore = useMainStore();
|
||||||
const flaschengeist = inject<FG_Plugin.Flaschengeist>('flaschengeist');
|
const flaschengeist = inject<FG_Plugin.Flaschengeist>('flaschengeist');
|
||||||
const leftDrawer = ref(false);
|
const leftDrawer = ref(true);
|
||||||
const leftDrawerMini = ref(false);
|
const leftDrawerMini = ref(false);
|
||||||
const shortcuts = flaschengeist?.shortcuts || [];
|
const shortcuts = flaschengeist?.shortcuts || [];
|
||||||
const mainLinks = flaschengeist?.menuLinks || [];
|
const mainLinks = flaschengeist?.menuLinks || [];
|
||||||
|
@ -135,22 +112,27 @@ export default defineComponent({
|
||||||
const useNative = 'Notification' in window && window.Notification !== undefined;
|
const useNative = 'Notification' in window && window.Notification !== undefined;
|
||||||
const noPermission = ref(!useNative || window.Notification.permission !== 'granted');
|
const noPermission = ref(!useNative || window.Notification.permission !== 'granted');
|
||||||
|
|
||||||
onBeforeMount(() => pollNotification());
|
|
||||||
onBeforeUnmount(() => window.clearInterval(polling.value));
|
|
||||||
|
|
||||||
const leftDrawerOpen = computed({
|
|
||||||
get: () => (leftDrawer.value || Screen.gt.sm ? true : false),
|
|
||||||
set: (val: boolean) => (leftDrawer.value = val),
|
|
||||||
});
|
|
||||||
|
|
||||||
const subLinks = computed(() => {
|
const subLinks = computed(() => {
|
||||||
const matched = router.currentRoute.value.matched[1];
|
const matched = router.currentRoute.value.matched[1];
|
||||||
return flaschengeist?.menuLinks.find((link) => matched.name == link.link)?.children;
|
return flaschengeist?.menuLinks.find((link) => matched.name == link.link)?.children;
|
||||||
});
|
});
|
||||||
|
|
||||||
function leftDrawerClicker() {
|
onBeforeMount(() => {
|
||||||
if (leftDrawerMini.value) {
|
polling.value = window.setInterval(() => pollNotification(), config.pollingInterval);
|
||||||
leftDrawerMini.value = false;
|
pollNotification();
|
||||||
|
});
|
||||||
|
onBeforeUnmount(() => window.clearInterval(polling.value));
|
||||||
|
|
||||||
|
function openMenu(event: { target: HTMLInputElement }) {
|
||||||
|
if (event.target.nodeName === 'DIV') leftDrawerMini.value = false;
|
||||||
|
else {
|
||||||
|
if (!leftDrawer.value || leftDrawerMini.value) {
|
||||||
|
leftDrawer.value = true;
|
||||||
|
leftDrawerMini.value = false;
|
||||||
|
} else {
|
||||||
|
leftDrawerMini.value = Screen.gt.sm && !leftDrawerMini.value;
|
||||||
|
leftDrawer.value = leftDrawerMini.value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -170,30 +152,28 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
function pollNotification() {
|
function pollNotification() {
|
||||||
polling.value = window.setInterval(() => {
|
void mainStore
|
||||||
void mainStore
|
.loadNotifications(<FG_Plugin.Flaschengeist>flaschengeist)
|
||||||
.loadNotifications(<FG_Plugin.Flaschengeist>flaschengeist)
|
.then((notifications) => {
|
||||||
.then((notifications) => {
|
if (useNative && !noPermission.value)
|
||||||
if (useNative && !noPermission.value)
|
notifications.forEach(
|
||||||
notifications.forEach(
|
(notif) =>
|
||||||
(notif) =>
|
new window.Notification(notif.text, {
|
||||||
new window.Notification(notif.text, {
|
timestamp: notif.time.getTime(),
|
||||||
timestamp: notif.time.getTime(),
|
})
|
||||||
})
|
);
|
||||||
);
|
});
|
||||||
});
|
|
||||||
}, config.pollingInterval);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
essentials,
|
essentials,
|
||||||
leftDrawerOpen,
|
leftDrawer,
|
||||||
leftDrawerMini,
|
leftDrawerMini,
|
||||||
leftDrawerClicker,
|
|
||||||
logout,
|
logout,
|
||||||
mainLinks,
|
mainLinks,
|
||||||
notifications,
|
notifications,
|
||||||
noPermission,
|
noPermission,
|
||||||
|
openMenu,
|
||||||
remove,
|
remove,
|
||||||
requestPermission,
|
requestPermission,
|
||||||
shortcuts,
|
shortcuts,
|
||||||
|
|
|
@ -6,20 +6,21 @@
|
||||||
</q-toolbar>
|
</q-toolbar>
|
||||||
|
|
||||||
<q-card-section>
|
<q-card-section>
|
||||||
<q-form ref="LoginForm" class="q-gutter-md" @submit="doLogin">
|
<q-form class="q-gutter-md" @submit="doLogin">
|
||||||
<q-input
|
<q-input
|
||||||
v-model="userid"
|
v-model="userid"
|
||||||
filled
|
filled
|
||||||
label="Benutzername oder E-Mail"
|
label="Benutzername oder E-Mail"
|
||||||
:rules="rules"
|
autocomplete="username"
|
||||||
|
:rules="[notEmpty]"
|
||||||
tabindex="1"
|
tabindex="1"
|
||||||
/>
|
/>
|
||||||
<q-input
|
<password-input
|
||||||
v-model="password"
|
v-model="password"
|
||||||
filled
|
filled
|
||||||
type="password"
|
label="Passwort"
|
||||||
label="Password"
|
autocomplete="cureent-password"
|
||||||
:rules="rules"
|
:rules="[notEmpty]"
|
||||||
tabindex="2"
|
tabindex="2"
|
||||||
/>
|
/>
|
||||||
<div class="row justify-between">
|
<div class="row justify-between">
|
||||||
|
@ -54,15 +55,18 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { useMainStore } from 'src/stores';
|
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { Loading, Notify } from 'quasar';
|
import { Loading, Notify } from 'quasar';
|
||||||
|
import { useMainStore } from 'src/stores';
|
||||||
import { defineComponent, ref } from 'vue';
|
import { defineComponent, ref } from 'vue';
|
||||||
import { setBaseURL, api } from 'boot/axios';
|
import { setBaseURL, api } from 'boot/axios';
|
||||||
|
import { notEmpty } from 'src/utils/validators';
|
||||||
import { useUserStore } from 'src/plugins/user/store';
|
import { useUserStore } from 'src/plugins/user/store';
|
||||||
|
import PasswordInput from 'src/components/utils/PasswordInput.vue';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'Login',
|
name: 'Login',
|
||||||
|
components: { PasswordInput },
|
||||||
setup() {
|
setup() {
|
||||||
const mainStore = useMainStore();
|
const mainStore = useMainStore();
|
||||||
const mainRoute = { name: 'dashboard' };
|
const mainRoute = { name: 'dashboard' };
|
||||||
|
@ -71,7 +75,6 @@ export default defineComponent({
|
||||||
/* Stuff for the real login page */
|
/* Stuff for the real login page */
|
||||||
const userid = ref('');
|
const userid = ref('');
|
||||||
const password = ref('');
|
const password = ref('');
|
||||||
const rules = [(val: string) => (val && val.length > 0) || 'Feld darf nicht leer sein!'];
|
|
||||||
const server = ref<string | undefined>(api.defaults.baseURL);
|
const server = ref<string | undefined>(api.defaults.baseURL);
|
||||||
const visible = ref(false);
|
const visible = ref(false);
|
||||||
|
|
||||||
|
@ -138,15 +141,15 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
userid,
|
changeUrl,
|
||||||
password,
|
|
||||||
doLogin,
|
doLogin,
|
||||||
doReset,
|
doReset,
|
||||||
rules,
|
notEmpty,
|
||||||
server,
|
|
||||||
changeUrl,
|
|
||||||
visible,
|
|
||||||
openServerSettings,
|
openServerSettings,
|
||||||
|
password,
|
||||||
|
server,
|
||||||
|
userid,
|
||||||
|
visible,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<q-page padding class="fit row justify-center content-start items-start q-gutter-sm">
|
||||||
<q-tabs v-if="$q.screen.gt.sm" v-model="tab">
|
<q-tabs v-if="$q.screen.gt.sm" v-model="tab">
|
||||||
<q-tab
|
<q-tab
|
||||||
v-for="(tabindex, index) in tabs"
|
v-for="(tabindex, index) in tabs"
|
||||||
|
@ -37,32 +37,30 @@
|
||||||
</div>
|
</div>
|
||||||
</q-list>
|
</q-list>
|
||||||
</q-drawer>
|
</q-drawer>
|
||||||
<q-page padding class="fit row justify-left q-col-gutter-sm">
|
<q-tab-panels
|
||||||
<q-tab-panels
|
v-model="tab"
|
||||||
v-model="tab"
|
style="background-color: transparent"
|
||||||
style="background-color: transparent"
|
class="q-pa-none col-12"
|
||||||
class="q-pa-none col-12"
|
animated
|
||||||
animated
|
>
|
||||||
>
|
<q-tab-panel name="add" class="q-px-xs">
|
||||||
<q-tab-panel name="add" class="q-px-xs">
|
<BalanceAdd
|
||||||
<BalanceAdd
|
@open-history="
|
||||||
@open-history="
|
showDrawer = !showDrawer;
|
||||||
showDrawer = !showDrawer;
|
show = true;
|
||||||
show = true;
|
"
|
||||||
"
|
/>
|
||||||
/>
|
</q-tab-panel>
|
||||||
</q-tab-panel>
|
<q-tab-panel name="transfer" class="q-px-xs">
|
||||||
<q-tab-panel name="transfer" class="q-px-xs">
|
<BalanceTransfer
|
||||||
<BalanceTransfer
|
@open-history="
|
||||||
@open-history="
|
showDrawer = !showDrawer;
|
||||||
showDrawer = !showDrawer;
|
show = true;
|
||||||
show = true;
|
"
|
||||||
"
|
/>
|
||||||
/>
|
</q-tab-panel>
|
||||||
</q-tab-panel>
|
</q-tab-panels>
|
||||||
</q-tab-panels>
|
</q-page>
|
||||||
</q-page>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
<template>
|
<template>
|
||||||
<q-card style="text-align: center">
|
<q-card style="text-align: center">
|
||||||
<q-card-section class="row justify-center content-stretch">
|
<q-card-section class="row justify-center content-stretch">
|
||||||
<div class="col-4">
|
<div v-if="avatar" class="col-4">
|
||||||
<div style="width: 100%; padding-bottom: 100%; position: relative">
|
<div style="width: 100%; padding-bottom: 100%; position: relative">
|
||||||
<q-avatar style="position: absolute; top: 0; left: 0; width: 100%; height: 100%">
|
<q-avatar style="position: absolute; top: 0; left: 0; width: 100%; height: 100%">
|
||||||
<img :src="avatarLink" />
|
<img :src="avatarLink" :onerror="error" />
|
||||||
</q-avatar>
|
</q-avatar>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -39,9 +39,14 @@ export default defineComponent({
|
||||||
// Ensure users are loaded,so we can query birthdays
|
// Ensure users are loaded,so we can query birthdays
|
||||||
onMounted(() => userStore.getUsers(false));
|
onMounted(() => userStore.getUsers(false));
|
||||||
|
|
||||||
const name = ref(mainStore.currentUser.display_name);
|
const avatar = ref(true);
|
||||||
|
const name = ref(mainStore.currentUser.firstname);
|
||||||
const avatarLink = ref(mainStore.currentUser.avatar_url);
|
const avatarLink = ref(mainStore.currentUser.avatar_url);
|
||||||
|
|
||||||
|
function error() {
|
||||||
|
avatar.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
function userHasBirthday(user: FG.User) {
|
function userHasBirthday(user: FG.User) {
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
return (
|
return (
|
||||||
|
@ -61,7 +66,7 @@ export default defineComponent({
|
||||||
.filter((user) => user.userid !== mainStore.currentUser.userid)
|
.filter((user) => user.userid !== mainStore.currentUser.userid)
|
||||||
);
|
);
|
||||||
|
|
||||||
return { avatarLink, name, hasBirthday, birthday };
|
return { avatar, avatarLink, error, name, hasBirthday, birthday };
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -2,44 +2,48 @@
|
||||||
<q-form @submit="save" @reset="reset">
|
<q-form @submit="save" @reset="reset">
|
||||||
<q-card-section class="fit row justify-start content-center items-center">
|
<q-card-section class="fit row justify-start content-center items-center">
|
||||||
<q-input
|
<q-input
|
||||||
v-model="user_model.firstname"
|
v-model="userModel.firstname"
|
||||||
class="col-xs-12 col-sm-6 q-pa-sm"
|
class="col-xs-12 col-sm-6 q-pa-sm"
|
||||||
label="Vorname"
|
label="Vorname"
|
||||||
:rules="[notEmpty]"
|
:rules="[notEmpty]"
|
||||||
|
autocomplete="given-name"
|
||||||
filled
|
filled
|
||||||
/>
|
/>
|
||||||
<q-input
|
<q-input
|
||||||
v-model="user_model.lastname"
|
v-model="userModel.lastname"
|
||||||
class="col-xs-12 col-sm-6 q-pa-sm"
|
class="col-xs-12 col-sm-6 q-pa-sm"
|
||||||
label="Nachname"
|
label="Nachname"
|
||||||
:rules="[notEmpty]"
|
:rules="[notEmpty]"
|
||||||
|
autocomplete="family-name"
|
||||||
filled
|
filled
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<q-input
|
<q-input
|
||||||
v-model="user_model.display_name"
|
v-model="userModel.display_name"
|
||||||
class="col-xs-12 col-sm-6 q-pa-sm"
|
class="col-xs-12 col-sm-6 q-pa-sm"
|
||||||
label="Angezeigter Name"
|
label="Angezeigter Name"
|
||||||
:rules="[notEmpty]"
|
:rules="[notEmpty]"
|
||||||
|
autocomplete="nickname"
|
||||||
filled
|
filled
|
||||||
/>
|
/>
|
||||||
<q-input
|
<q-input
|
||||||
v-model="user_model.mail"
|
v-model="userModel.mail"
|
||||||
class="col-xs-12 col-sm-6 q-pa-sm"
|
class="col-xs-12 col-sm-6 q-pa-sm"
|
||||||
label="E-Mail"
|
label="E-Mail"
|
||||||
:rules="[isEmail, notEmpty]"
|
:rules="[isEmail, notEmpty]"
|
||||||
|
autocomplete="email"
|
||||||
filled
|
filled
|
||||||
/>
|
/>
|
||||||
<q-input
|
<q-input
|
||||||
v-model="user_model.userid"
|
v-model="userModel.userid"
|
||||||
class="col-xs-12 col-sm-6 q-pa-sm"
|
class="col-xs-12 col-sm-6 q-pa-sm"
|
||||||
label="Benutzername"
|
label="Benutzername"
|
||||||
:readonly="!newUser"
|
:readonly="!newUser"
|
||||||
:rules="[isUseridUsed, notEmpty]"
|
:rules="newUser ? [isFreeUID, notEmpty] : []"
|
||||||
|
autocomplete="username"
|
||||||
filled
|
filled
|
||||||
/>
|
/>
|
||||||
<q-select
|
<q-select
|
||||||
v-model="user_model.roles"
|
v-model="userModel.roles"
|
||||||
class="col-xs-12 col-sm-6 q-pa-sm"
|
class="col-xs-12 col-sm-6 q-pa-sm"
|
||||||
label="Rollen"
|
label="Rollen"
|
||||||
filled
|
filled
|
||||||
|
@ -51,9 +55,10 @@
|
||||||
option-value="name"
|
option-value="name"
|
||||||
/>
|
/>
|
||||||
<IsoDateInput
|
<IsoDateInput
|
||||||
v-model="user_model.birthday"
|
v-model="userModel.birthday"
|
||||||
class="col-xs-12 col-sm-6 q-pa-sm"
|
class="col-xs-12 col-sm-6 q-pa-sm"
|
||||||
label="Geburtstag"
|
label="Geburtstag"
|
||||||
|
autocomplete="bday"
|
||||||
/>
|
/>
|
||||||
<q-file
|
<q-file
|
||||||
v-model="avatar"
|
v-model="avatar"
|
||||||
|
@ -72,31 +77,22 @@
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-separator v-if="!newUser" />
|
<q-separator v-if="!newUser" />
|
||||||
<q-card-section v-if="!newUser" class="fit row justify-start content-center items-center">
|
<q-card-section v-if="!newUser" class="fit row justify-start content-center items-center">
|
||||||
<q-input
|
<PasswordInput
|
||||||
v-if="isCurrentUser"
|
v-if="isCurrentUser"
|
||||||
v-model="password"
|
v-model="password"
|
||||||
class="col-xs-12 col-sm-6 col-md-4 q-pa-sm"
|
|
||||||
label="Password"
|
|
||||||
type="password"
|
|
||||||
hint="Password muss immer eingetragen werden"
|
|
||||||
:rules="[notEmpty]"
|
:rules="[notEmpty]"
|
||||||
filled
|
filled
|
||||||
|
label="Passwort"
|
||||||
|
autocomplete="current-password"
|
||||||
|
class="col-xs-12 col-sm-6 q-pa-sm"
|
||||||
|
hint="Passwort muss immer eingetragen werden"
|
||||||
/>
|
/>
|
||||||
<q-input
|
<PasswordInput
|
||||||
v-model="new_password"
|
v-model="newPassword"
|
||||||
class="col-xs-12 col-sm-6 col-md-4 q-pa-sm"
|
filled
|
||||||
label="Neues Password"
|
label="Neues Password"
|
||||||
type="password"
|
autocomplete="new-password"
|
||||||
filled
|
class="col-xs-12 col-sm-6 q-pa-sm"
|
||||||
/>
|
|
||||||
<q-input
|
|
||||||
v-model="new_password2"
|
|
||||||
class="col-xs-12 col-sm-6 col-md-4 q-pa-sm"
|
|
||||||
label="Wiederhole neues Password"
|
|
||||||
type="password"
|
|
||||||
:disable="new_password.length == 0"
|
|
||||||
:rules="[samePassword]"
|
|
||||||
filled
|
|
||||||
/>
|
/>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-card-actions align="right">
|
<q-card-actions align="right">
|
||||||
|
@ -109,14 +105,16 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Notify } from 'quasar';
|
import { Notify } from 'quasar';
|
||||||
import { hasPermission } from 'src/utils/permission';
|
import { hasPermission } from 'src/utils/permission';
|
||||||
|
import { notEmpty, isEmail } from 'src/utils/validators';
|
||||||
import IsoDateInput from 'src/components/utils/IsoDateInput.vue';
|
import IsoDateInput from 'src/components/utils/IsoDateInput.vue';
|
||||||
import { defineComponent, computed, ref, onBeforeMount, PropType } from 'vue';
|
import PasswordInput from 'src/components/utils/PasswordInput.vue';
|
||||||
|
import { defineComponent, computed, ref, onBeforeMount, PropType, watch } from 'vue';
|
||||||
import { useUserStore } from '../../store';
|
import { useUserStore } from '../../store';
|
||||||
import { useMainStore } from 'src/stores';
|
import { useMainStore } from 'src/stores';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'MainUserSettings',
|
name: 'MainUserSettings',
|
||||||
components: { IsoDateInput },
|
components: { IsoDateInput, PasswordInput },
|
||||||
props: {
|
props: {
|
||||||
user: {
|
user: {
|
||||||
required: true,
|
required: true,
|
||||||
|
@ -131,17 +129,25 @@ export default defineComponent({
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const mainStore = useMainStore();
|
const mainStore = useMainStore();
|
||||||
|
|
||||||
const user_model = ref(props.user);
|
|
||||||
|
|
||||||
onBeforeMount(() => {
|
onBeforeMount(() => {
|
||||||
void userStore.getRoles(false);
|
void userStore.getRoles(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
const isCurrentUser = computed(() => user_model.value.userid === mainStore.currentUser.userid);
|
const password = ref('');
|
||||||
|
const newPassword = ref('');
|
||||||
|
const avatar = ref<File | FileList | string[]>();
|
||||||
|
const userModel = ref(props.user);
|
||||||
|
|
||||||
const canSetRoles = computed(() => hasPermission('users_set_roles'));
|
const canSetRoles = computed(() => hasPermission('users_set_roles'));
|
||||||
|
const allRoles = computed(() => userStore.roles.map((role) => role.name));
|
||||||
|
const isCurrentUser = computed(() => userModel.value.userid === mainStore.currentUser.userid);
|
||||||
|
|
||||||
|
/* Reset model if props changed */
|
||||||
|
watch(
|
||||||
|
() => props.user,
|
||||||
|
() => (userModel.value = props.user)
|
||||||
|
);
|
||||||
|
|
||||||
const avatar = ref([] as string[]);
|
|
||||||
function onAvatarRejected() {
|
function onAvatarRejected() {
|
||||||
Notify.create({
|
Notify.create({
|
||||||
group: false,
|
group: false,
|
||||||
|
@ -151,84 +157,61 @@ export default defineComponent({
|
||||||
progress: true,
|
progress: true,
|
||||||
actions: [{ icon: 'mdi-close', color: 'white' }],
|
actions: [{ icon: 'mdi-close', color: 'white' }],
|
||||||
});
|
});
|
||||||
avatar.value = [];
|
avatar.value = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const allRoles = computed(() => userStore.roles.map((role) => role.name));
|
|
||||||
const password = ref('');
|
|
||||||
const new_password = ref('');
|
|
||||||
const new_password2 = ref('');
|
|
||||||
|
|
||||||
function save() {
|
function save() {
|
||||||
let changed = user_model.value;
|
let changed = userModel.value;
|
||||||
if (typeof changed.birthday === 'string') changed.birthday = new Date(changed.birthday);
|
if (typeof changed.birthday === 'string') changed.birthday = new Date(changed.birthday);
|
||||||
changed = Object.assign(changed, {
|
changed = Object.assign(changed, {
|
||||||
password: password.value,
|
password: password.value,
|
||||||
});
|
});
|
||||||
if (new_password.value != '') {
|
if (newPassword.value != '') {
|
||||||
changed = Object.assign(changed, {
|
changed = Object.assign(changed, {
|
||||||
new_password: new_password.value,
|
new_password: newPassword.value,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
emit('update:user', changed);
|
emit('update:user', changed);
|
||||||
|
|
||||||
if (avatar.value && (avatar.value.length > 0 || avatar.value instanceof File))
|
if (avatar.value)
|
||||||
userStore.uploadAvatar(changed, avatar.value[0]).catch((response: Response) => {
|
userStore
|
||||||
if (response && response.status == 400) {
|
.uploadAvatar(changed, avatar.value instanceof File ? avatar.value : avatar.value[0])
|
||||||
onAvatarRejected();
|
.catch((response: Response) => {
|
||||||
}
|
if (response && response.status == 400) {
|
||||||
});
|
onAvatarRejected();
|
||||||
|
}
|
||||||
|
});
|
||||||
reset();
|
reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
function reset() {
|
function reset() {
|
||||||
user_model.value = props.user;
|
userModel.value = props.user;
|
||||||
password.value = '';
|
password.value = '';
|
||||||
new_password.value = '';
|
newPassword.value = '';
|
||||||
new_password2.value = '';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function samePassword(val: string) {
|
function isFreeUID(val: string) {
|
||||||
return val == new_password.value || 'Passwörter sind nicht identisch!';
|
|
||||||
}
|
|
||||||
|
|
||||||
function notEmpty(val: string) {
|
|
||||||
return !!val || 'Feld darf nicht leer sein!';
|
|
||||||
}
|
|
||||||
|
|
||||||
function isEmail(val: string | null) {
|
|
||||||
return (
|
return (
|
||||||
!val || /^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w\w+)+$/.test(val) || 'E-Mail ist nicht valide.'
|
userStore.users.findIndex((user) => user.userid === val) === -1 ||
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function isUseridUsed(val: string) {
|
|
||||||
return (
|
|
||||||
!userStore.users.find((user: FG.User) => {
|
|
||||||
return user.userid == val;
|
|
||||||
}) ||
|
|
||||||
!props.newUser ||
|
|
||||||
'Benutzername ist schon vergeben'
|
'Benutzername ist schon vergeben'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
avatar,
|
|
||||||
user_model,
|
|
||||||
onAvatarRejected,
|
|
||||||
allRoles,
|
allRoles,
|
||||||
|
avatar,
|
||||||
canSetRoles,
|
canSetRoles,
|
||||||
password,
|
|
||||||
new_password,
|
|
||||||
new_password2,
|
|
||||||
samePassword,
|
|
||||||
isCurrentUser,
|
isCurrentUser,
|
||||||
isEmail,
|
isEmail,
|
||||||
|
isFreeUID,
|
||||||
|
newPassword,
|
||||||
notEmpty,
|
notEmpty,
|
||||||
isUseridUsed,
|
onAvatarRejected,
|
||||||
save,
|
password,
|
||||||
reset,
|
reset,
|
||||||
|
save,
|
||||||
|
userModel,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -22,15 +22,15 @@
|
||||||
/>
|
/>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-separator />
|
<q-separator />
|
||||||
<q-card-section v-if="role" class="fit row justify-start content-center items-center">
|
<q-card-section v-if="role">
|
||||||
<q-scroll-area style="height: 20em; width: 100%">
|
<q-input v-if="role.id !== -1" v-model="newRoleName" filled label="neuer Name" />
|
||||||
<q-input v-if="role.id != -1" v-model="newRoleName" filled label="neuer Name" />
|
<q-scroll-area style="height: 40vh; width: 100%" class="background-like-input">
|
||||||
<q-option-group
|
<q-option-group
|
||||||
:model-value="role.permissions"
|
:model-value="role.permissions"
|
||||||
:options="permissions"
|
:options="permissions"
|
||||||
color="primary"
|
color="primary"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
@input="updatePermissions"
|
@update:modelValue="updatePermissions"
|
||||||
/>
|
/>
|
||||||
</q-scroll-area>
|
</q-scroll-area>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
|
@ -143,3 +143,18 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="sass" scoped>
|
||||||
|
// Same colors like qinput with filled attribute set
|
||||||
|
.body--light .background-like-input
|
||||||
|
background-color: rgba(0, 0, 0, 0.05)
|
||||||
|
&:hover
|
||||||
|
background: rgba(0,0,0,.1)
|
||||||
|
border-bottom: 1px solid rgba(0, 0, 0, 0.42)
|
||||||
|
|
||||||
|
.body--dark .background-like-input
|
||||||
|
background-color: rgba(255, 255, 255, 0.07)
|
||||||
|
&:hover
|
||||||
|
background: rgba(255, 255, 255, 0.14)
|
||||||
|
border-bottom: 1px solid #fff
|
||||||
|
</style>
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<q-page padding class="fit row justify-center content-start items-start q-gutter-sm">
|
||||||
<q-tabs v-if="$q.screen.gt.sm" v-model="tab">
|
<q-tabs v-if="$q.screen.gt.sm" v-model="tab">
|
||||||
<q-tab
|
<q-tab
|
||||||
v-for="(tabindex, index) in tabs"
|
v-for="(tabindex, index) in tabs"
|
||||||
|
@ -24,25 +24,23 @@
|
||||||
</q-item>
|
</q-item>
|
||||||
</q-list>
|
</q-list>
|
||||||
</q-drawer>
|
</q-drawer>
|
||||||
<q-page padding class="fit row justify-center content-start items-start q-gutter-sm">
|
<q-tab-panels
|
||||||
<q-tab-panels
|
v-model="tab"
|
||||||
v-model="tab"
|
style="background-color: transparent"
|
||||||
style="background-color: transparent"
|
class="q-ma-none q-pa-none fit row justify-center content-start items-start"
|
||||||
class="q-ma-none q-pa-none fit row justify-center content-start items-start"
|
animated
|
||||||
animated
|
>
|
||||||
>
|
<q-tab-panel name="user">
|
||||||
<q-tab-panel name="user">
|
<UpdateUser />
|
||||||
<UpdateUser />
|
</q-tab-panel>
|
||||||
</q-tab-panel>
|
<q-tab-panel name="newUser">
|
||||||
<q-tab-panel name="newUser">
|
<NewUser />
|
||||||
<NewUser />
|
</q-tab-panel>
|
||||||
</q-tab-panel>
|
<q-tab-panel name="roles">
|
||||||
<q-tab-panel name="roles">
|
<RoleSettings v-if="canEditRoles" />
|
||||||
<RoleSettings v-if="canEditRoles" />
|
</q-tab-panel>
|
||||||
</q-tab-panel>
|
</q-tab-panels>
|
||||||
</q-tab-panels>
|
</q-page>
|
||||||
</q-page>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
|
|
@ -64,7 +64,7 @@ export const useUserStore = defineStore({
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
|
|
||||||
async uploadAvatar(user: FG.User, file: string) {
|
async uploadAvatar(user: FG.User, file: string | File) {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('file', file);
|
formData.append('file', file);
|
||||||
await api.post(`/users/${user.userid}/avatar`, formData, {
|
await api.post(`/users/${user.userid}/avatar`, formData, {
|
||||||
|
|
|
@ -15,3 +15,9 @@ export function stringIsTime(val: string) {
|
||||||
export function stringIsDateTime(val: string) {
|
export function stringIsDateTime(val: string) {
|
||||||
return !val || /^\d{4}-\d\d-\d\d \d\d:\d\d$/.test(val) || 'Datum und Zeit ist nicht gültig.';
|
return !val || /^\d{4}-\d\d-\d\d \d\d:\d\d$/.test(val) || 'Datum und Zeit ist nicht gültig.';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isEmail(val: string) {
|
||||||
|
return (
|
||||||
|
!val || /^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w\w+)+$/.test(val) || 'E-Mail ist nicht gültig.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue