Merge remote-tracking branch 'origin/develop' into feature/pricelist

This commit is contained in:
Tim Gröger 2021-03-29 20:22:50 +02:00
commit 3eea079871
70 changed files with 344 additions and 150 deletions

View File

@ -10,12 +10,11 @@
"lint": "eslint --ext .js,.ts,.vue ./src" "lint": "eslint --ext .js,.ts,.vue ./src"
}, },
"dependencies": { "dependencies": {
"@quasar/extras": "^1.9.19",
"axios": "^0.21.1", "axios": "^0.21.1",
"cordova": "^10.0.0", "cordova": "^10.0.0",
"core-js": "^3.9.1", "core-js": "^3.9.1",
"pinia": "^2.0.0-alpha.7", "pinia": "^2.0.0-alpha.7",
"quasar": "^2.0.0-beta.9" "quasar": "^2.0.0-beta.11"
}, },
"prettier": { "prettier": {
"singleQuote": true, "singleQuote": true,
@ -24,7 +23,8 @@
"arrowParens": "always" "arrowParens": "always"
}, },
"devDependencies": { "devDependencies": {
"@quasar/app": "^3.0.0-beta.9", "@quasar/app": "^3.0.0-beta.10",
"@quasar/extras": "^1.10.0",
"@quasar/quasar-app-extension-qcalendar": "file:deps/quasar-ui-qcalendar/app-extension", "@quasar/quasar-app-extension-qcalendar": "file:deps/quasar-ui-qcalendar/app-extension",
"@types/node": "^12.20.6", "@types/node": "^12.20.6",
"@types/webpack": "^4.41.26", "@types/webpack": "^4.41.26",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 913 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 411 B

After

Width:  |  Height:  |  Size: 370 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 715 B

After

Width:  |  Height:  |  Size: 702 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1022 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 866 B

After

Width:  |  Height:  |  Size: 801 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1017 B

After

Width:  |  Height:  |  Size: 969 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 566 B

After

Width:  |  Height:  |  Size: 529 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1015 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 715 B

After

Width:  |  Height:  |  Size: 702 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 923 B

After

Width:  |  Height:  |  Size: 885 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 996 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

View File

@ -313,7 +313,7 @@ async function getBackend() {
*/ */
export default boot(async ({ router, app }) => { export default boot(async ({ router, app }) => {
const backend = await getBackend(); const backend = await getBackend();
if (!backend) { if (backend === null) {
void router.push({ name: 'error' }); void router.push({ name: 'error' });
return; return;
} }

View File

@ -0,0 +1,45 @@
<template>
<q-card bordered class="row q-ma-xs q-pa-xs" style="position: relative">
<div class="col-12 text-weight-light">{{ dateString }}</div>
<div class="col-12">{{ modelValue.text }}</div>
<q-btn
round
dense
icon="close"
size="sm"
color="negative"
class="q-ma-xs"
style="position: absolute; top: 0; right: 0"
@click="remove"
/>
</q-card>
</template>
<script lang="ts">
import { defineComponent, PropType, computed } from 'vue';
import { formatDateTime } from 'src/utils/datetime';
export default defineComponent({
name: 'Notification',
props: {
modelValue: {
required: true,
type: Object as PropType<FG.Notification>,
},
},
emits: {
remove: (id: number) => !!id,
},
setup(props, { emit }) {
const dateString = computed(() => formatDateTime(props.modelValue.time, true, true));
function remove() {
emit('remove', props.modelValue.id);
}
return { dateString, remove };
},
});
</script>
<style scoped></style>

View File

@ -1,11 +1,10 @@
declare namespace FG { declare namespace FG {
interface Session { interface Notification {
expires: Date; id: number;
token: string; plugin: string;
lifetime: number; text: string;
browser: string; data?: unknown;
platform: string; time: Date;
userid: string;
} }
interface User { interface User {
userid: string; userid: string;
@ -18,6 +17,14 @@ declare namespace FG {
permissions?: Array<string>; permissions?: Array<string>;
avatar_url?: string; avatar_url?: string;
} }
interface Session {
expires: Date;
token: string;
lifetime: number;
browser: string;
platform: string;
userid: string;
}
type Permission = string; type Permission = string;
interface Role { interface Role {
id: number; id: number;
@ -69,6 +76,7 @@ declare namespace FG {
} }
interface Service { interface Service {
userid: string; userid: string;
is_backup: boolean;
value: number; value: number;
} }
interface Drink { interface Drink {
@ -77,8 +85,8 @@ declare namespace FG {
package_size?: number; package_size?: number;
name: string; name: string;
volume?: number; volume?: number;
cost_price_pro_volume?: number; cost_per_volume?: number;
cost_price_package_netto?: number; cost_per_package?: number;
tags?: Array<Tag>; tags?: Array<Tag>;
type?: DrinkType; type?: DrinkType;
volumes: Array<DrinkPriceVolume>; volumes: Array<DrinkPriceVolume>;
@ -88,7 +96,7 @@ declare namespace FG {
interface DrinkIngredient { interface DrinkIngredient {
id: number; id: number;
volume: number; volume: number;
drink_ingredient_id: number; ingredient_id: number;
} }
interface DrinkPrice { interface DrinkPrice {
id: number; id: number;

View File

@ -21,6 +21,25 @@
</q-toolbar-title> </q-toolbar-title>
<!-- Hier kommen die Shortlinks hin --> <!-- Hier kommen die Shortlinks hin -->
<q-btn icon="message" flat dense
><q-badge color="negative" floating>{{ notifications.length }}</q-badge>
<q-menu style="max-height: 400px; overflow: auto">
<q-btn
v-if="noPermission"
label="Benachrichtigungen erlauben"
@click="requestPermission"
/>
<template v-if="notifications.length > 0">
<Notification
v-for="(notification, index) in notifications"
:key="index"
:model-value="notification"
@remove="remove"
/>
</template>
<div v-else class="q-pa-sm">Keine neuen Benachrichtigungen</div>
</q-menu>
</q-btn>
<shortcut-link <shortcut-link
v-for="(shortcut, index) in shortcuts" v-for="(shortcut, index) in shortcuts"
:key="'shortcut' + index" :key="'shortcut' + index"
@ -84,8 +103,9 @@
<script lang="ts"> <script lang="ts">
import EssentialLink from 'src/components/navigation/EssentialLink.vue'; import EssentialLink from 'src/components/navigation/EssentialLink.vue';
import ShortcutLink from 'src/components/navigation/ShortcutLink.vue'; import ShortcutLink from 'src/components/navigation/ShortcutLink.vue';
import Notification from 'src/components/Notification.vue';
import { Screen } from 'quasar'; import { Screen } from 'quasar';
import { defineComponent, ref, inject, computed } from 'vue'; import { defineComponent, ref, inject, computed, onBeforeMount } from 'vue';
import { useMainStore } from 'src/store'; import { useMainStore } from 'src/store';
import { FG_Plugin } from 'src/plugins'; import { FG_Plugin } from 'src/plugins';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
@ -100,7 +120,7 @@ const essentials: FG_Plugin.MenuLink[] = [
export default defineComponent({ export default defineComponent({
name: 'MainLayout', name: 'MainLayout',
components: { EssentialLink, ShortcutLink }, components: { EssentialLink, ShortcutLink, Notification },
setup() { setup() {
const router = useRouter(); const router = useRouter();
const mainStore = useMainStore(); const mainStore = useMainStore();
@ -108,6 +128,16 @@ export default defineComponent({
const leftDrawer = ref(false); const leftDrawer = ref(false);
const shortcuts = flaschengeist?.shortcuts; const shortcuts = flaschengeist?.shortcuts;
const mainLinks = flaschengeist?.menuLinks; const mainLinks = flaschengeist?.menuLinks;
const notifications = computed(() => mainStore.notifications.slice().reverse());
const noPermission = ref(window.Notification.permission !== 'granted');
function requestPermission() {
void window.Notification.requestPermission().then(
(p) => (noPermission.value = p !== 'granted')
);
}
onBeforeMount(() => window.setInterval(() => void mainStore.loadNotifications(), 30000));
const leftDrawerOpen = computed({ const leftDrawerOpen = computed({
get: () => (leftDrawer.value || Screen.gt.sm ? true : false), get: () => (leftDrawer.value || Screen.gt.sm ? true : false),
@ -131,6 +161,10 @@ export default defineComponent({
void mainStore.logout(); void mainStore.logout();
} }
async function remove(id: number) {
await mainStore.removeNotification(id);
}
return { return {
essentials, essentials,
leftDrawerOpen, leftDrawerOpen,
@ -138,6 +172,10 @@ export default defineComponent({
leftDrawerClicker, leftDrawerClicker,
logout, logout,
mainLinks, mainLinks,
notifications,
noPermission,
remove,
requestPermission,
shortcuts, shortcuts,
subLinks, subLinks,
}; };

View File

@ -181,7 +181,7 @@ export default defineComponent({
id: -1, id: -1,
drink_ingredient: { drink_ingredient: {
id: -1, id: -1,
drink_ingredient_id: newIngredient.value.id, ingredient_id: newIngredient.value.id,
volume: newIngredientVolume.value, volume: newIngredientVolume.value,
}, },
extra_ingredient: undefined, extra_ingredient: undefined,

View File

@ -41,14 +41,14 @@
type="number" type="number"
/> />
<q-input <q-input
v-model.number="newDrink.cost_price_package_netto" v-model.number="newDrink.cost_per_package"
class="col-sm-4 col-xs-6 q-pa-sm" class="col-sm-4 col-xs-6 q-pa-sm"
filled filled
label="Preis Netto/Gebinde" label="Preis Netto/Gebinde"
type="number" type="number"
/> />
<q-input <q-input
v-model="cost_price_pro_volume" v-model.number="cost_per_volume"
class="col-sm-4 col-xs-6 q-pa-sm" class="col-sm-4 col-xs-6 q-pa-sm"
filled filled
label="Preis mit 19%/Liter" label="Preis mit 19%/Liter"
@ -78,8 +78,8 @@ export default defineComponent({
package_size: undefined, package_size: undefined,
name: '', name: '',
volume: undefined, volume: undefined,
cost_price_pro_volume: undefined, cost_per_volume: undefined,
cost_price_package_netto: undefined, cost_per_package: undefined,
tags: [], tags: [],
type: undefined, type: undefined,
volumes: [], volumes: [],
@ -88,24 +88,24 @@ export default defineComponent({
const calc_price_pro_volume = computed( const calc_price_pro_volume = computed(
() => () =>
!!newDrink.value.cost_price_package_netto && !!newDrink.value.cost_per_package &&
!!newDrink.value.volume && !!newDrink.value.volume &&
!!newDrink.value.package_size !!newDrink.value.package_size
); );
const cost_price_pro_volume = computed({ const cost_per_volume = computed({
get: () => { get: () => {
if (calc_price_pro_volume.value) { if (calc_price_pro_volume.value) {
const retVal = const retVal =
((newDrink.value.cost_price_package_netto || 0) / ((newDrink.value.cost_per_package || 0) /
((newDrink.value.volume || 0) * (newDrink.value.package_size || 0))) * ((newDrink.value.volume || 0) * (newDrink.value.package_size || 0))) *
1.19; 1.19;
// eslint-disable-next-line vue/no-side-effects-in-computed-properties // eslint-disable-next-line vue/no-side-effects-in-computed-properties
newDrink.value.cost_price_pro_volume = Math.round(retVal * 1000) / 1000; newDrink.value.cost_per_volume = Math.round(retVal * 1000) / 1000;
} }
return newDrink.value.cost_price_pro_volume; return newDrink.value.cost_per_volume;
}, },
set: (val) => { set: (val) => {
newDrink.value.cost_price_pro_volume = val; newDrink.value.cost_per_volume = val;
}, },
}); });
@ -124,7 +124,7 @@ export default defineComponent({
drinkTypes: computed(() => store.drinkTypes), drinkTypes: computed(() => store.drinkTypes),
newDrink, newDrink,
calc_price_pro_volume, calc_price_pro_volume,
cost_price_pro_volume, cost_per_volume,
addDrink, addDrink,
cancelAddDrink, cancelAddDrink,
notEmpty, notEmpty,

View File

@ -45,7 +45,7 @@ export default defineComponent({
volume.value.ingredients.forEach((ingredient) => { volume.value.ingredients.forEach((ingredient) => {
if (ingredient.drink_ingredient) { if (ingredient.drink_ingredient) {
const _drink = store.drinks.find( const _drink = store.drinks.find(
(a) => a.id === ingredient.drink_ingredient?.drink_ingredient_id (a) => a.id === ingredient.drink_ingredient?.ingredient_id
); );
retVal += retVal +=
ingredient.drink_ingredient.volume * ingredient.drink_ingredient.volume *

View File

@ -0,0 +1,100 @@
<template>
<q-page padding>
<q-card>
<q-table title="Getränke" :columns="columns_drinks" :rows="drinks" row-key="name">
<template #body-cell-volumes="props">
<q-td :props="props">
<q-table
:columns="columns_volumes"
:rows="props.row.volumes"
hide-header
:hide-bottom="props.row.volumes.length < 5"
flat
>
<template #body-cell-prices="props_volumes">
<q-td :props="props_volumes">
<q-table
:columns="columns_prices"
:rows="props_volumes.row.prices"
hide-header
:hide-bottom="props_volumes.row.prices.length < 5"
flat
>
<template #body-cell-public="props_prices">
<q-td :props="props_prices">
<q-toggle v-model="props_prices.row.public" disable />
</q-td>
</template>
</q-table>
</q-td>
</template>
</q-table>
</q-td>
</template>
</q-table>
</q-card>
</q-page>
</template>
<script lang="ts">
import { defineComponent, onBeforeMount, computed } from 'vue';
import { usePricelistStore } from '../store';
export default defineComponent({
name: 'Pricelist',
setup() {
const store = usePricelistStore();
onBeforeMount(() => {
void store.getDrinks();
});
const drinks = computed(() => store.drinks);
const columns_drinks = [
{
name: 'name',
label: 'Getränk',
field: 'name',
align: 'center',
},
{
name: 'volumes',
label: 'Preise',
field: 'volumes',
align: 'center',
},
];
const columns_volumes = [
{
name: 'volume',
label: 'Inhalt',
field: 'volume',
format: (val: number) => `${val.toFixed(3)}L`,
align: 'left',
},
{
name: 'prices',
label: 'Preise',
field: 'prices',
},
];
const columns_prices = [
{
name: 'price',
label: 'Preis',
field: 'price',
format: (val: number) => `${val.toFixed(2)}`,
},
{
name: 'description',
label: 'Beschreibung',
field: 'description',
},
{
name: 'public',
label: 'Öffentlich',
field: 'public',
},
];
return { columns_drinks, columns_volumes, columns_prices, drinks };
},
});
</script>

View File

@ -1,15 +0,0 @@
<template>
<Pricelist />
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import Pricelist from '../components/Pricelist.vue';
export default defineComponent({
name: 'PricelistPage',
components: { Pricelist },
});
</script>
<style scoped></style>

View File

@ -19,7 +19,7 @@ export const innerRoutes: FG_Plugin.MenuRoute[] = [
route: { route: {
path: 'pricelist', path: 'pricelist',
name: 'drinks-pricelist', name: 'drinks-pricelist',
component: () => import('../pages/PricelistP.vue'), component: () => import('../pages/Pricelist.vue'),
}, },
}, },
{ {

View File

@ -53,8 +53,8 @@ class Drink {
package_size, package_size,
name, name,
volume, volume,
cost_price_pro_volume, cost_per_volume,
cost_price_package_netto, cost_per_package,
tags, tags,
type, type,
uuid, uuid,
@ -65,15 +65,13 @@ class Drink {
this.package_size = package_size; this.package_size = package_size;
this.name = name; this.name = name;
this.volume = volume; this.volume = volume;
this.cost_price_package_netto = cost_price_package_netto; this.cost_per_package = cost_per_package;
this._cost_price_pro_volume = cost_price_pro_volume; this.cost_per_volume = cost_per_volume;
this.cost_price_pro_volume = computed({ this.cost_price_pro_volume = computed({
get: () => { get: () => {
if (!!this.volume && !!this.package_size && !!this.cost_price_package_netto) { if (!!this.volume && !!this.package_size && !!this.cost_per_package) {
const retVal = const retVal =
((this.cost_price_package_netto || 0) / ((this.cost_per_package || 0) / ((this.volume || 0) * (this.package_size || 0))) * 1.19;
((this.volume || 0) * (this.package_size || 0))) *
1.19;
this._cost_price_pro_volume = Math.round(retVal * 1000) / 1000; this._cost_price_pro_volume = Math.round(retVal * 1000) / 1000;
} }
@ -289,7 +287,7 @@ export const usePricelistStore = defineStore({
volume.ingredients.forEach((ingredient) => { volume.ingredients.forEach((ingredient) => {
if (ingredient.drink_ingredient) { if (ingredient.drink_ingredient) {
const _drink = usePricelistStore().drinks.find( const _drink = usePricelistStore().drinks.find(
(a) => a.id === ingredient.drink_ingredient?.drink_ingredient_id (a) => a.id === ingredient.drink_ingredient?.ingredient_id
); );
retVal += retVal +=
ingredient.drink_ingredient.volume * ingredient.drink_ingredient.volume *

View File

@ -15,32 +15,30 @@
</q-card> </q-card>
</q-dialog> </q-dialog>
<q-page padding> <q-card>
<q-card> <q-card-section>
<q-card-section> <q-table title="Veranstaltungstypen" :rows="rows" row-key="jobid" :columns="columns">
<q-table title="Veranstaltungstypen" :data="rows" row-key="jobid" :columns="columns"> <template #top-right>
<template #top-right> <q-input v-model="newEventType" dense placeholder="Neuer Typ" />
<q-input v-model="newEventType" dense placeholder="Neuer Typ" />
<div></div> <div></div>
<q-btn color="primary" icon="mdi-plus" label="Hinzufügen" @click="addType" /> <q-btn color="primary" icon="mdi-plus" label="Hinzufügen" @click="addType" />
</template> </template>
<template #body-cell-actions="props"> <template #body-cell-actions="props">
<!-- <q-btn :label="item"> --> <!-- <q-btn :label="item"> -->
<!-- {{ item.row.name }} --> <!-- {{ item.row.name }} -->
<q-td :props="props" align="right" :auto-width="true"> <q-td :props="props" align="right" :auto-width="true">
<q-btn <q-btn
round round
icon="mdi-pencil" icon="mdi-pencil"
@click="editType({ id: props.row.id, name: props.row.name })" @click="editType({ id: props.row.id, name: props.row.name })"
/> />
<q-btn round icon="mdi-delete" @click="deleteType(props.row.id)" /> <q-btn round icon="mdi-delete" @click="deleteType(props.row.id)" />
</q-td> </q-td>
</template> </template>
</q-table> </q-table>
</q-card-section> </q-card-section>
</q-card> </q-card>
</q-page>
</div> </div>
</template> </template>
@ -59,7 +57,7 @@ export default defineComponent({
const actualEvent = ref(emptyEvent); const actualEvent = ref(emptyEvent);
const newEventName = ref(''); const newEventName = ref('');
onBeforeMount(() => store.getEventTypes()); onBeforeMount(async () => await store.getEventTypes());
const rows = computed(() => store.eventTypes); const rows = computed(() => store.eventTypes);

View File

@ -15,32 +15,30 @@
</q-card> </q-card>
</q-dialog> </q-dialog>
<q-page padding> <q-card>
<q-card> <q-card-section>
<q-card-section> <q-table title="Diensttypen" :rows="rows" row-key="jobid" :columns="columns">
<q-table title="Diensttypen" :data="rows" row-key="jobid" :columns="columns"> <template #top-right>
<template #top-right> <q-input v-model="newJob" dense placeholder="Neuer Typ" />
<q-input v-model="newJob" dense placeholder="Neuer Typ" />
<div></div> <div></div>
<q-btn color="primary" icon="mdi-plus" label="Hinzufügen" @click="addType" /> <q-btn color="primary" icon="mdi-plus" label="Hinzufügen" @click="addType" />
</template> </template>
<template #body-cell-actions="props"> <template #body-cell-actions="props">
<!-- <q-btn :label="item"> --> <!-- <q-btn :label="item"> -->
<!-- {{ item.row.name }} --> <!-- {{ item.row.name }} -->
<q-td :props="props" align="right" :auto-width="true"> <q-td :props="props" align="right" :auto-width="true">
<q-btn <q-btn
round round
icon="mdi-pencil" icon="mdi-pencil"
@click="editType({ id: props.row.id, name: props.row.name })" @click="editType({ id: props.row.id, name: props.row.name })"
/> />
<q-btn round icon="mdi-delete" @click="deleteType(props.row.id)" /> <q-btn round icon="mdi-delete" @click="deleteType(props.row.id)" />
</q-td> </q-td>
</template> </template>
</q-table> </q-table>
</q-card-section> </q-card-section>
</q-card> </q-card>
</q-page>
</div> </div>
</template> </template>

View File

@ -84,6 +84,7 @@ export default defineComponent({
async function enrollForJob() { async function enrollForJob() {
const newService: FG.Service = { const newService: FG.Service = {
userid: mainStore.currentUser.userid, userid: mainStore.currentUser.userid,
is_backup: false,
value: 1, value: 1,
}; };
try { try {
@ -104,6 +105,7 @@ export default defineComponent({
async function signOutFromJob() { async function signOutFromJob() {
const newService: FG.Service = { const newService: FG.Service = {
userid: mainStore.currentUser.userid, userid: mainStore.currentUser.userid,
is_backup: false,
value: -1, value: -1,
}; };
try { try {

View File

@ -1,16 +1,16 @@
export const PERMISSIONS = { export const PERMISSIONS = {
// Can create events // Can create events
CREATE: 'schedule_create', CREATE: 'events_create',
// Can edit events // Can edit events
EDIT: 'schedule_edit', EDIT: 'events_edit',
// Can delete events // Can delete events
DELETE: 'schedule_delete', DELETE: 'events_delete',
// Can create and edit EventTypes // Can create and edit EventTypes
EVENT_TYPE: 'schedule_event_type', EVENT_TYPE: 'events_event_type',
// Can create and edit JobTypes // Can create and edit JobTypes
JOB_TYPE: 'schedule_job_type', JOB_TYPE: 'events_job_type',
// Can self assign to jobs // Can self assign to jobs
ASSIGN: 'schedule_assign', ASSIGN: 'events_assign',
// Can assign other users to jobs // Can assign other users to jobs
ASSIGN_OTHER: 'schedule_assign_other', ASSIGN_OTHER: 'events_assign_other',
}; };

View File

@ -6,7 +6,7 @@ const plugin: FG_Plugin.Plugin = {
name: 'User', name: 'User',
innerRoutes: routes, innerRoutes: routes,
requiredModules: [], requiredModules: [],
requiredBackendModules: ['auth'], requiredBackendModules: ['auth', 'users', 'roles'],
version: '0.0.1', version: '0.0.1',
widgets: [ widgets: [
{ {

View File

@ -41,6 +41,7 @@ export const useMainStore = defineStore({
state: () => ({ state: () => ({
session: loadCurrentSession(), session: loadCurrentSession(),
user: loadUser(), user: loadUser(),
notifications: [] as Array<FG.Notification>,
}), }),
getters: { getters: {
@ -123,5 +124,33 @@ export const useMainStore = defineStore({
response && 'status' in response ? (<AxiosResponse>response).status : false response && 'status' in response ? (<AxiosResponse>response).status : false
); );
}, },
async loadNotifications() {
const params =
this.notifications.length > 0
? { from: this.notifications[this.notifications.length - 1].time }
: {};
const { data } = await api.get<FG.Notification[]>('/notifications', { params: params });
data.forEach((n) => {
n.time = new Date(n.time);
if (window.Notification.permission === 'granted')
new window.Notification(n.text, {
timestamp: n.time.getTime(),
});
});
this.notifications.push(...data);
},
async removeNotification(id: number) {
const idx = this.notifications.findIndex((n) => n.id === id);
if (idx >= 0)
try {
this.notifications.splice(idx, 1);
await api.delete(`/notifications/${id}`);
} catch (error) {
if (this.notifications.length > idx)
this.notifications.splice(idx, this.notifications.length - idx - 1);
}
},
}, },
}); });

View File

@ -1027,10 +1027,10 @@
resolved "https://registry.yarnpkg.com/@positron/stack-trace/-/stack-trace-1.0.0.tgz#14fcc712a530038ef9be1ce6952315a839f466a8" resolved "https://registry.yarnpkg.com/@positron/stack-trace/-/stack-trace-1.0.0.tgz#14fcc712a530038ef9be1ce6952315a839f466a8"
integrity sha1-FPzHEqUwA475vhzmlSMVqDn0Zqg= integrity sha1-FPzHEqUwA475vhzmlSMVqDn0Zqg=
"@quasar/app@^3.0.0-beta.9": "@quasar/app@^3.0.0-beta.10":
version "3.0.0-beta.9" version "3.0.0-beta.10"
resolved "https://registry.yarnpkg.com/@quasar/app/-/app-3.0.0-beta.9.tgz#8d5fb2056aaf5a03919b63c9c655933281e8beed" resolved "https://registry.yarnpkg.com/@quasar/app/-/app-3.0.0-beta.10.tgz#f4401fa64f9f94b7e643560fb8968f3f7250578d"
integrity sha512-Wt12LLWTyp1GMw17xVVScvjom9EILIlnuV4xrgZ7WTfwwZ18jcqtf3pTs2NMeOgIwohlP2qLsI5JQW9wLylQzw== integrity sha512-KC5dmW7hOJ0x634vgyb98cjjia2Mj0BySgq1JghMGiCqq3noF6E9DS9UoF3ru9HUVRUtxfLe73Su8V0s1UoQZg==
dependencies: dependencies:
"@quasar/babel-preset-app" "2.0.1" "@quasar/babel-preset-app" "2.0.1"
"@quasar/fastclick" "1.1.4" "@quasar/fastclick" "1.1.4"
@ -1041,7 +1041,7 @@
"@types/terser-webpack-plugin" "3.0.0" "@types/terser-webpack-plugin" "3.0.0"
"@types/webpack" "4.41.26" "@types/webpack" "4.41.26"
"@types/webpack-bundle-analyzer" "3.9.1" "@types/webpack-bundle-analyzer" "3.9.1"
"@types/webpack-dev-server" "3.11.1" "@types/webpack-dev-server" "3.11.2"
"@vue/compiler-sfc" "3.0.6" "@vue/compiler-sfc" "3.0.6"
"@vue/server-renderer" "3.0.6" "@vue/server-renderer" "3.0.6"
archiver "5.2.0" archiver "5.2.0"
@ -1135,10 +1135,10 @@
core-js "^3.6.5" core-js "^3.6.5"
core-js-compat "^3.6.5" core-js-compat "^3.6.5"
"@quasar/extras@^1.9.19": "@quasar/extras@^1.10.0":
version "1.9.19" version "1.10.0"
resolved "https://registry.yarnpkg.com/@quasar/extras/-/extras-1.9.19.tgz#8e511117ccd8ec5bbed7038b9dad1749052376b8" resolved "https://registry.yarnpkg.com/@quasar/extras/-/extras-1.10.0.tgz#1cf13d299e80e1396b2f982e1663b89dd54f3e8e"
integrity sha512-A1IO0dzfUtRkyKq3QC7ZQNvhLeJey2ET8MvRX7oV0Qe+g30jy/4pr1ZhO7GylkDAoLT3C9eY8t2/5Anrl0AHdA== integrity sha512-H71Y/6pxunwiEN+oo9OBGM96ncM2QreVdnb2t2iStVHduju3nnypw9euBCshhYxKE/ORHZoOBRDoiddUOyaUdA==
"@quasar/fastclick@1.1.4": "@quasar/fastclick@1.1.4":
version "1.1.4" version "1.1.4"
@ -1148,7 +1148,7 @@
"@quasar/quasar-app-extension-qcalendar@file:deps/quasar-ui-qcalendar/app-extension": "@quasar/quasar-app-extension-qcalendar@file:deps/quasar-ui-qcalendar/app-extension":
version "4.0.0-alpha.1" version "4.0.0-alpha.1"
dependencies: dependencies:
"@quasar/quasar-ui-qcalendar" "link:../../../../../.cache/yarn/v6/npm-@quasar-quasar-app-extension-qcalendar-4.0.0-alpha.1-0eac22e6-3d93-4296-9ecf-77ea02e6ad1a-1616339967660/node_modules/@quasar/ui" "@quasar/quasar-ui-qcalendar" "link:../../../../../.cache/yarn/v6/npm-@quasar-quasar-app-extension-qcalendar-4.0.0-alpha.1-3c86cc72-2d95-4dfe-9e1e-2f553d66f91e-1616893646708/node_modules/@quasar/ui"
"@quasar/quasar-ui-qcalendar@link:deps/quasar-ui-qcalendar/ui": "@quasar/quasar-ui-qcalendar@link:deps/quasar-ui-qcalendar/ui":
version "0.0.0" version "0.0.0"
@ -1266,13 +1266,6 @@
resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz#3c9ee980f1a10d6021ae6632ca3e79ca2ec4fb50" resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz#3c9ee980f1a10d6021ae6632ca3e79ca2ec4fb50"
integrity sha512-giAlZwstKbmvMk1OO7WXSj4OZ0keXAcl2TQq4LWHiiPH2ByaH7WeUzng+Qej8UPxxv+8lRTuouo0iaNDBuzIBA== integrity sha512-giAlZwstKbmvMk1OO7WXSj4OZ0keXAcl2TQq4LWHiiPH2ByaH7WeUzng+Qej8UPxxv+8lRTuouo0iaNDBuzIBA==
"@types/http-proxy-middleware@*":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@types/http-proxy-middleware/-/http-proxy-middleware-1.0.0.tgz#4370a52766782e9c4f0be2ef79c3dd47aef5f428"
integrity sha512-/s8lFX6rT43hSPqjjD8KNuu0SkPKY7uIdR6u9DCxVqCRhAvfKxGbVOixJsAT2mdpSnCyrGFAGoB39KFh6tmRxw==
dependencies:
http-proxy-middleware "*"
"@types/http-proxy@^1.17.4": "@types/http-proxy@^1.17.4":
version "1.17.5" version "1.17.5"
resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.5.tgz#c203c5e6e9dc6820d27a40eb1e511c70a220423d" resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.5.tgz#c203c5e6e9dc6820d27a40eb1e511c70a220423d"
@ -1365,16 +1358,16 @@
dependencies: dependencies:
"@types/webpack" "*" "@types/webpack" "*"
"@types/webpack-dev-server@3.11.1": "@types/webpack-dev-server@3.11.2":
version "3.11.1" version "3.11.2"
resolved "https://registry.yarnpkg.com/@types/webpack-dev-server/-/webpack-dev-server-3.11.1.tgz#f8f4dac1da226d530bd15a1d5dc34b23ba766ccb" resolved "https://registry.yarnpkg.com/@types/webpack-dev-server/-/webpack-dev-server-3.11.2.tgz#73915a7d9e0a9b5e010a2388a46f68ab3f770ef8"
integrity sha512-rIb+LtUkKnh7+oIJm3WiMJONd71Q0lZuqGLcSqhZ5qjN9gV/CNmZe7Bai+brnBPZ/KVYOsr+4bFLiNZwjBicLw== integrity sha512-13w1VhaghN+G1rYjkBPgN/GFRoHd9uI2fwK9cSKvLutdmZ22L9iicFEvt69by40DP2I6uNcClaGTyPY6nYhIgQ==
dependencies: dependencies:
"@types/connect-history-api-fallback" "*" "@types/connect-history-api-fallback" "*"
"@types/express" "*" "@types/express" "*"
"@types/http-proxy-middleware" "*"
"@types/serve-static" "*" "@types/serve-static" "*"
"@types/webpack" "*" "@types/webpack" "*"
http-proxy-middleware "^1.0.0"
"@types/webpack-env@^1.16.0": "@types/webpack-env@^1.16.0":
version "1.16.0" version "1.16.0"
@ -5235,17 +5228,6 @@ http-proxy-agent@^4.0.1:
agent-base "6" agent-base "6"
debug "4" debug "4"
http-proxy-middleware@*:
version "1.0.6"
resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-1.0.6.tgz#0618557722f450375d3796d701a8ac5407b3b94e"
integrity sha512-NyL6ZB6cVni7pl+/IT2W0ni5ME00xR0sN27AQZZrpKn1b+qRh+mLbBxIq9Cq1oGfmTc7BUq4HB77mxwCaxAYNg==
dependencies:
"@types/http-proxy" "^1.17.4"
http-proxy "^1.18.1"
is-glob "^4.0.1"
lodash "^4.17.20"
micromatch "^4.0.2"
http-proxy-middleware@0.19.1: http-proxy-middleware@0.19.1:
version "0.19.1" version "0.19.1"
resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz#183c7dc4aa1479150306498c210cdaf96080a43a" resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz#183c7dc4aa1479150306498c210cdaf96080a43a"
@ -5256,6 +5238,17 @@ http-proxy-middleware@0.19.1:
lodash "^4.17.11" lodash "^4.17.11"
micromatch "^3.1.10" micromatch "^3.1.10"
http-proxy-middleware@^1.0.0:
version "1.0.6"
resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-1.0.6.tgz#0618557722f450375d3796d701a8ac5407b3b94e"
integrity sha512-NyL6ZB6cVni7pl+/IT2W0ni5ME00xR0sN27AQZZrpKn1b+qRh+mLbBxIq9Cq1oGfmTc7BUq4HB77mxwCaxAYNg==
dependencies:
"@types/http-proxy" "^1.17.4"
http-proxy "^1.18.1"
is-glob "^4.0.1"
lodash "^4.17.20"
micromatch "^4.0.2"
http-proxy@^1.17.0, http-proxy@^1.18.1: http-proxy@^1.17.0, http-proxy@^1.18.1:
version "1.18.1" version "1.18.1"
resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549" resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549"
@ -8236,10 +8229,10 @@ qs@~6.5.2:
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==
quasar@^2.0.0-beta.9: quasar@^2.0.0-beta.11:
version "2.0.0-beta.9" version "2.0.0-beta.11"
resolved "https://registry.yarnpkg.com/quasar/-/quasar-2.0.0-beta.9.tgz#0247a3512f3bdffd29f8f2a3f33fc91e62a91697" resolved "https://registry.yarnpkg.com/quasar/-/quasar-2.0.0-beta.11.tgz#78f21abe94caa78fe17ad17ce945d09dfda9e5d2"
integrity sha512-xqYKBQMF5ntM9RUr/eHFsSM6onXTzpwTcoBnM0YL6/QknBlSBHXltjG7pJmKMSm62ZRq1YeOdLsC9nchHgSrCQ== integrity sha512-YG+iVkd1LNbo0MFSrPl1npEW02FfVeD4+/98nYPz3pfZEdOy1kiOF7N9Ij7RDC8x0/+9Ans9mgBvs0xjg9YxyA==
query-string@^4.1.0: query-string@^4.1.0:
version "4.3.4" version "4.3.4"