Move software files one directory up, Readme
This commit is contained in:
88
src/App.vue
Normal file
88
src/App.vue
Normal file
@@ -0,0 +1,88 @@
|
||||
<script setup lang="ts">
|
||||
import { useTheme } from 'vuetify/lib/framework.mjs';
|
||||
import { i18n } from './plugins/i18n';
|
||||
import { ref, watch } from 'vue';
|
||||
import { usePreferencesStore } from './stores/preferences.store';
|
||||
import { useFeedbackStore } from './stores/feedback.store';
|
||||
import companyFooter from './components/navigation/companyFooter.vue';
|
||||
import urlBar from './components/navigation/urlBar.vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import NavigationBar from './components/navigation/navigationBar.vue';
|
||||
import { BannerStateEnum } from './data/enums/bannerStateEnum';
|
||||
|
||||
const preferencesStore = usePreferencesStore()
|
||||
const feedbackStore = useFeedbackStore()
|
||||
const theme = useTheme()
|
||||
const router = useRouter()
|
||||
|
||||
theme.global.name.value = preferencesStore.theme
|
||||
|
||||
// Global watcher
|
||||
// Watch for language change
|
||||
watch(() => preferencesStore.language, () => {
|
||||
i18n.global.locale = preferencesStore.language
|
||||
}, { immediate: true })
|
||||
|
||||
// Watch for theme change
|
||||
watch(() => preferencesStore.theme, () => {
|
||||
theme.global.name.value = preferencesStore.theme
|
||||
feedbackStore.addSnackbar(BannerStateEnum.ERROR)
|
||||
})
|
||||
|
||||
// Watch for 404 page directions
|
||||
watch(() => feedbackStore.notFound, () => {
|
||||
if (feedbackStore.notFound) {
|
||||
feedbackStore.notFound = false
|
||||
router.push("/404")
|
||||
}
|
||||
})
|
||||
|
||||
// Watch for snackbar disappear
|
||||
watch(() => feedbackStore.showSnackbar, () => {
|
||||
if (!feedbackStore.showSnackbar) {
|
||||
feedbackStore.snackbars = []
|
||||
}
|
||||
})
|
||||
|
||||
function calcMargin(i) {
|
||||
return (i * 60) + 10 + 'px'
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-app>
|
||||
<!-- Browser Navigation bar replica -->
|
||||
<url-bar />
|
||||
|
||||
<!-- Navigaion bar of page -->
|
||||
<navigation-bar />
|
||||
|
||||
|
||||
<v-main>
|
||||
<!-- Snackbar in the top right corner for user feedback -->
|
||||
<v-snackbar
|
||||
v-for="(s, i) in feedbackStore.snackbars"
|
||||
v-model="feedbackStore.showSnackbar"
|
||||
:key="i"
|
||||
timeout="3000"
|
||||
location="top right"
|
||||
:color="s.color"
|
||||
:style="{ 'margin-top': calcMargin(i) }"
|
||||
:icon="s.icon"
|
||||
close
|
||||
>
|
||||
<v-icon :icon="s.icon" />
|
||||
{{ s.text }}
|
||||
</v-snackbar>
|
||||
|
||||
<!-- Here changes the router the content -->
|
||||
<v-container max-width="1400" min-height="1000" class="py-0" height="100%">
|
||||
<v-sheet color="sheet" height="100%">
|
||||
<router-view></router-view>
|
||||
</v-sheet>
|
||||
</v-container>
|
||||
|
||||
<company-footer />
|
||||
</v-main>
|
||||
</v-app>
|
||||
</template>
|
||||
35
src/components/basics/actionDialog.vue
Normal file
35
src/components/basics/actionDialog.vue
Normal file
@@ -0,0 +1,35 @@
|
||||
<script setup lang="ts">
|
||||
import { ModelRef } from 'vue';
|
||||
import cardView from './cardView.vue';
|
||||
|
||||
const showDialog: ModelRef<boolean> = defineModel()
|
||||
|
||||
defineProps({
|
||||
title: String,
|
||||
icon: {
|
||||
type: String
|
||||
},
|
||||
subtitle: {
|
||||
type: String
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-dialog max-width="1200" v-model="showDialog">
|
||||
<card-view
|
||||
:title="title"
|
||||
:subtitle="subtitle"
|
||||
:icon="icon"
|
||||
:tonal="false"
|
||||
>
|
||||
<template #borderless>
|
||||
<slot></slot>
|
||||
</template>
|
||||
|
||||
<template #actions v-if="$slots.actions">
|
||||
<slot name="actions"></slot>
|
||||
</template>
|
||||
</card-view>
|
||||
</v-dialog>
|
||||
</template>
|
||||
64
src/components/basics/cardView.vue
Normal file
64
src/components/basics/cardView.vue
Normal file
@@ -0,0 +1,64 @@
|
||||
<script setup lang="ts">
|
||||
defineProps({
|
||||
title: String,
|
||||
subtitle: String,
|
||||
icon: {
|
||||
type: String
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
tonal: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: "primary"
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-card
|
||||
:variant="tonal ? 'tonal' : 'elevated'"
|
||||
>
|
||||
<v-card-title
|
||||
v-if="title || loading"
|
||||
class="pa-0"
|
||||
>
|
||||
<v-sheet :color="color">
|
||||
<v-skeleton-loader
|
||||
type="heading"
|
||||
:loading="loading"
|
||||
style="background-color: transparent"
|
||||
>
|
||||
<div>
|
||||
<div class="px-2 py-1 d-flex justify-center">
|
||||
<v-icon :icon="icon" v-if="icon" /> {{ title }}
|
||||
</div>
|
||||
<div>
|
||||
<v-card-subtitle >{{ subtitle }}</v-card-subtitle>
|
||||
</div>
|
||||
</div>
|
||||
</v-skeleton-loader>
|
||||
</v-sheet>
|
||||
</v-card-title>
|
||||
|
||||
<slot name="borderless" v-if="$slots.borderless"></slot>
|
||||
|
||||
<v-container v-if="$slots.default">
|
||||
<v-row>
|
||||
<v-col>
|
||||
<slot></slot>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
|
||||
<v-card-actions v-if="$slots.actions">
|
||||
<v-spacer />
|
||||
<slot name="actions"></slot>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</template>
|
||||
104
src/components/basics/cardViewHorizontal.vue
Normal file
104
src/components/basics/cardViewHorizontal.vue
Normal file
@@ -0,0 +1,104 @@
|
||||
<script setup lang="ts">
|
||||
defineProps({
|
||||
/** Image to display on the left side (if prepend slot is not in use) */
|
||||
image: String,
|
||||
|
||||
title: String,
|
||||
|
||||
/** Make the CardView click- and hoverable */
|
||||
link: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
|
||||
/** Displays a Skeleton Loader if true */
|
||||
loading: Boolean,
|
||||
|
||||
/** Height of the card, default 140px */
|
||||
height: {
|
||||
type: Number,
|
||||
default: 140
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-card
|
||||
variant="tonal"
|
||||
:link="link"
|
||||
:height="height"
|
||||
>
|
||||
<v-row
|
||||
no-gutters
|
||||
>
|
||||
<!-- First col for image or prepend text -->
|
||||
<v-col
|
||||
:cols="!$slots.prepend ? 'auto' : 2"
|
||||
>
|
||||
<v-skeleton-loader
|
||||
v-if="!$slots.prepend"
|
||||
type="image"
|
||||
:loading="loading"
|
||||
>
|
||||
<v-img
|
||||
:src="image"
|
||||
:height="height"
|
||||
:width="height"
|
||||
cover
|
||||
/>
|
||||
</v-skeleton-loader>
|
||||
|
||||
<v-skeleton-loader
|
||||
v-else
|
||||
type="text"
|
||||
:loading="loading"
|
||||
>
|
||||
<v-sheet
|
||||
:height="height"
|
||||
width="100%"
|
||||
class="text-center d-flex justify-center align-center"
|
||||
>
|
||||
<slot name="prepend" />
|
||||
</v-sheet>
|
||||
</v-skeleton-loader>
|
||||
</v-col>
|
||||
|
||||
<v-divider vertical v-if="$slots.prepend" />
|
||||
|
||||
<!-- Second col for main content -->
|
||||
<v-col
|
||||
class="pl-2"
|
||||
>
|
||||
<v-skeleton-loader
|
||||
:loading="loading"
|
||||
type="sentences"
|
||||
>
|
||||
<v-sheet
|
||||
:height="height"
|
||||
>
|
||||
<div>
|
||||
<div class="text-h4 font-weight-black pt-2 h-100">
|
||||
{{ title }}
|
||||
</div>
|
||||
|
||||
<div class="text-disabled">
|
||||
<slot name="content" />
|
||||
</div>
|
||||
</div>
|
||||
</v-sheet>
|
||||
</v-skeleton-loader>
|
||||
</v-col>
|
||||
|
||||
<v-divider vertical />
|
||||
|
||||
<!-- Third col for append content after the divider -->
|
||||
<v-col
|
||||
class="text-center d-flex justify-center align-center"
|
||||
cols="3"
|
||||
lg="2"
|
||||
>
|
||||
<slot name="append"></slot>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card>
|
||||
</template>
|
||||
26
src/components/basics/cardViewOneLine.vue
Normal file
26
src/components/basics/cardViewOneLine.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<script setup lang="ts">
|
||||
defineProps({
|
||||
/** Displayed smaller text on the left side */
|
||||
descriptionText: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
|
||||
/** Displayed bigger text on the right side */
|
||||
valueText: [ String, Number ]
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-card variant="outlined" class="my-1 mx-2 px-2">
|
||||
<v-row class="d-flex justify-center align-center">
|
||||
<v-col class="text-caption text-left" v-if="descriptionText.length > 0">
|
||||
{{ descriptionText }}
|
||||
</v-col>
|
||||
|
||||
<v-col class="text-h5 font-weight-bold text-right">
|
||||
{{ valueText }}
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card>
|
||||
</template>
|
||||
76
src/components/basics/cardViewTopImage.vue
Normal file
76
src/components/basics/cardViewTopImage.vue
Normal file
@@ -0,0 +1,76 @@
|
||||
<script setup lang="ts">
|
||||
defineProps({
|
||||
image: String,
|
||||
errorImage: {
|
||||
type: String,
|
||||
default: "artists/unknown-artist.jpg"
|
||||
},
|
||||
title: String,
|
||||
smallerTitle: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
link: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
loading: Boolean
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-card
|
||||
variant="tonal"
|
||||
:link="link"
|
||||
>
|
||||
<v-skeleton-loader
|
||||
:loading="loading"
|
||||
type="image"
|
||||
height="150"
|
||||
>
|
||||
<v-img
|
||||
:src="image"
|
||||
aspect-ratio="1"
|
||||
max-height="200"
|
||||
cover
|
||||
>
|
||||
<template #error>
|
||||
<v-img
|
||||
:src="'http://localhost:3000/static/' + errorImage"
|
||||
aspect-ratio="1"
|
||||
style="background-color: aliceblue;"
|
||||
/>
|
||||
</template>
|
||||
</v-img>
|
||||
</v-skeleton-loader>
|
||||
|
||||
<v-skeleton-loader
|
||||
:loading="loading"
|
||||
type="heading"
|
||||
>
|
||||
<div v-if="title">
|
||||
<v-card-title v-if="!smallerTitle">
|
||||
{{ title }}
|
||||
</v-card-title>
|
||||
|
||||
<v-card-title v-else style="font-size: medium">
|
||||
{{ title }}
|
||||
</v-card-title>
|
||||
</div>
|
||||
</v-skeleton-loader>
|
||||
|
||||
<v-skeleton-loader
|
||||
type="sentences"
|
||||
:loading="loading"
|
||||
>
|
||||
<div class="px-4 pb-4 text-disabled" v-if="$slots.default">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</v-skeleton-loader>
|
||||
|
||||
<v-card-actions v-if="$slots.actions" class="card-actions position-absolute bottom-0 right-0">
|
||||
<v-spacer />
|
||||
<slot name="actions"></slot>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</template>
|
||||
11
src/components/basics/circularProgressIndeterminate.vue
Normal file
11
src/components/basics/circularProgressIndeterminate.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-progress-circular
|
||||
size="128"
|
||||
width="12"
|
||||
color="primary"
|
||||
indeterminate
|
||||
/>
|
||||
</template>
|
||||
55
src/components/basics/confirmDialog.vue
Normal file
55
src/components/basics/confirmDialog.vue
Normal file
@@ -0,0 +1,55 @@
|
||||
<script setup lang="ts">
|
||||
import { ModelRef } from 'vue';
|
||||
import actionDialog from './../basics/actionDialog.vue';
|
||||
import outlinedButton from './../basics/outlinedButton.vue';
|
||||
|
||||
const showDialog: ModelRef<boolean> = defineModel()
|
||||
|
||||
const props = defineProps({
|
||||
title: String,
|
||||
description: String,
|
||||
onConfirm: Function,
|
||||
loading: Boolean
|
||||
})
|
||||
|
||||
function confirmPressed() {
|
||||
props.onConfirm()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<action-dialog
|
||||
:title="title"
|
||||
max-width="400"
|
||||
v-model="showDialog"
|
||||
persistent
|
||||
>
|
||||
<v-container>
|
||||
<v-row>
|
||||
<v-col>
|
||||
{{ description }}
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
|
||||
<template #actions>
|
||||
<outlined-button
|
||||
@click="showDialog = false"
|
||||
prepend-icon="mdi-close"
|
||||
color="orange"
|
||||
:loading="loading"
|
||||
>
|
||||
{{ $t("misc.actions.cancel") }}
|
||||
</outlined-button>
|
||||
|
||||
<outlined-button
|
||||
@click="confirmPressed"
|
||||
prepend-icon="mdi-check"
|
||||
color="red"
|
||||
:loading="loading"
|
||||
>
|
||||
{{ $t("misc.actions.confirm") }}
|
||||
</outlined-button>
|
||||
</template>
|
||||
</action-dialog>
|
||||
</template>
|
||||
19
src/components/basics/outlinedButton.vue
Normal file
19
src/components/basics/outlinedButton.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
defineProps({
|
||||
prependIcon: String,
|
||||
color: {
|
||||
type: String,
|
||||
default: "secondary"
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-btn
|
||||
:prepend-icon="prependIcon"
|
||||
variant="outlined"
|
||||
:color="color"
|
||||
>
|
||||
<slot></slot>
|
||||
</v-btn>
|
||||
</template>
|
||||
47
src/components/basics/sectionDivider.vue
Normal file
47
src/components/basics/sectionDivider.vue
Normal file
@@ -0,0 +1,47 @@
|
||||
<script setup lang="ts">
|
||||
defineProps({
|
||||
title: String,
|
||||
image: String,
|
||||
loading: Boolean
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-row class="pt-3 d-none d-md-flex">
|
||||
<v-col class="d-flex justify-center align-center">
|
||||
<v-sheet height="12" width="100%" color="primary" class="rounded-s-lg" />
|
||||
</v-col>
|
||||
|
||||
<v-col class="v-col-auto">
|
||||
<v-skeleton-loader
|
||||
type="heading"
|
||||
:loading="loading"
|
||||
width="300"
|
||||
>
|
||||
<span class="text-h4">{{ title }}</span>
|
||||
</v-skeleton-loader>
|
||||
</v-col>
|
||||
|
||||
<v-col class="d-flex justify-center align-center">
|
||||
<v-sheet height="12" width="100%" color="primary" class="rounded-e-lg" />
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row class="d-md-none">
|
||||
<v-col>
|
||||
<v-skeleton-loader
|
||||
type="heading"
|
||||
:loading="loading"
|
||||
class="d-flex justify-center align-center"
|
||||
>
|
||||
<span class="text-h4 text-center">{{ title }}</span>
|
||||
</v-skeleton-loader>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row class="d-md-none">
|
||||
<v-col class="d-flex justify-center align-center">
|
||||
<v-sheet height="12" width="80%" color="primary" class="rounded-pill" />
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
63
src/components/navigation/companyFooter.vue
Normal file
63
src/components/navigation/companyFooter.vue
Normal file
@@ -0,0 +1,63 @@
|
||||
<script setup lang="ts">
|
||||
import { useExerciseStore } from '@/stores/exercise.store';
|
||||
import { ref, watch } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
const route = useRoute()
|
||||
const routeItems = ref(route.path.split('/'))
|
||||
const exerciseStore = useExerciseStore()
|
||||
|
||||
watch(() => route.path, () => {
|
||||
routeItems.value = route.path.split("/")
|
||||
routeItems.value = routeItems.value.filter(value => value != "")
|
||||
|
||||
for (let item in routeItems.value) {
|
||||
item.charAt(0).toUpperCase() + item.slice(1)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-footer
|
||||
color="primary"
|
||||
absolute
|
||||
inset
|
||||
app
|
||||
>
|
||||
<v-container>
|
||||
<!-- Current location -->
|
||||
<v-row class="w-100">
|
||||
<v-col class="d-flex justify-center">
|
||||
<v-breadcrumbs
|
||||
:items="routeItems"
|
||||
v-if="routeItems.length != 0 && routeItems[0] != ''"
|
||||
>
|
||||
<template v-slot:title="{ item }">
|
||||
<v-card variant="outlined" class="pa-2" rounded="0">
|
||||
{{ item.title.charAt(0).toUpperCase() + item.title.slice(1) }}
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<template v-slot:divider>
|
||||
<v-icon icon="mdi-forward"></v-icon>
|
||||
</template>
|
||||
</v-breadcrumbs>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<!-- Attributes -->
|
||||
<v-row>
|
||||
<v-col class="d-flex justify-center">
|
||||
<div v-for="query in route.query" v-html="query" />
|
||||
|
||||
<!-- Logic to check, if exercise 3.1 is solved -->
|
||||
<div v-for="query in route.query">
|
||||
<span v-if="String(query).startsWith('<iframe')">
|
||||
{{ exerciseStore.solveExercise(3, 1) }}
|
||||
</span>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</v-footer>
|
||||
</template>
|
||||
50
src/components/navigation/navigationAppendItems.vue
Normal file
50
src/components/navigation/navigationAppendItems.vue
Normal file
@@ -0,0 +1,50 @@
|
||||
<script setup lang="ts">
|
||||
import { useAccountStore } from '@/stores/account.store';
|
||||
import { useBasketStore } from '@/stores/basket.store';
|
||||
import { useExerciseStore } from '@/stores/exercise.store';
|
||||
|
||||
const accountStore = useAccountStore()
|
||||
const basketStore = useBasketStore()
|
||||
const exerciseStore = useExerciseStore()
|
||||
|
||||
exerciseStore.getAllExercises()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-btn variant="plain" icon="mdi-magnify" to="/search" />
|
||||
|
||||
<v-btn
|
||||
v-if="accountStore.userAccountToken == ''"
|
||||
variant="plain"
|
||||
icon="mdi-account"
|
||||
to="/account/login"
|
||||
/>
|
||||
|
||||
<v-btn v-else variant="plain" icon="mdi-account-check" to="/account/home" />
|
||||
|
||||
<div>
|
||||
<v-badge
|
||||
:content="basketStore.itemsInBasket.reduce((tot, item) => {
|
||||
return tot + item.seats.length
|
||||
}, 0)"
|
||||
color="red" offset-x="8" offset-y="8">
|
||||
<v-btn variant="plain" icon="mdi-cart" to="/basket" />
|
||||
</v-badge>
|
||||
</div>
|
||||
|
||||
<v-btn
|
||||
v-if="accountStore.adminPanelVisible"
|
||||
variant="plain"
|
||||
icon="mdi-table-cog"
|
||||
to="/admin"
|
||||
/>
|
||||
|
||||
<v-btn
|
||||
v-if="exerciseStore.helpPageVisible"
|
||||
variant="plain"
|
||||
icon="mdi-help"
|
||||
to="/help"
|
||||
/>
|
||||
|
||||
<v-btn variant="plain" icon="mdi-cog" to="/preferences"/>
|
||||
</template>
|
||||
21
src/components/navigation/navigationBar.vue
Normal file
21
src/components/navigation/navigationBar.vue
Normal file
@@ -0,0 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
import navigationPrependItems from './navigationPrependItems.vue';
|
||||
import navigationAppendItems from './navigationAppendItems.vue';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-app-bar
|
||||
height="80"
|
||||
color="primary"
|
||||
class="px-5"
|
||||
elevation="0"
|
||||
>
|
||||
<template #prepend>
|
||||
<navigation-prepend-items />
|
||||
</template>
|
||||
|
||||
<template #append>
|
||||
<navigation-append-items />
|
||||
</template>
|
||||
</v-app-bar>
|
||||
</template>
|
||||
90
src/components/navigation/navigationPrependItems.vue
Normal file
90
src/components/navigation/navigationPrependItems.vue
Normal file
@@ -0,0 +1,90 @@
|
||||
<script setup lang="ts">
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-card
|
||||
:link="true"
|
||||
rounded="0"
|
||||
elevation="0"
|
||||
class="mr-5"
|
||||
to="/"
|
||||
>
|
||||
<v-img src="logo.png" width="80" />
|
||||
</v-card>
|
||||
|
||||
<div class="d-md-none">
|
||||
|
||||
<v-menu>
|
||||
<template #activator="{ props }">
|
||||
<v-btn
|
||||
icon="mdi-menu"
|
||||
v-bind="props"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<v-list>
|
||||
<v-list-item
|
||||
to="/bands"
|
||||
prepend-icon="mdi-guitar-electric"
|
||||
>
|
||||
{{ $t('band.allBands', 2) }}
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item
|
||||
to="/concerts"
|
||||
prepend-icon="mdi-ticket"
|
||||
>
|
||||
{{ $t('concert.allConcerts', 2) }}
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item
|
||||
to="/locations"
|
||||
prepend-icon="mdi-city"
|
||||
>
|
||||
{{ $t('location.allLocations', 2) }}
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<v-divider vertical class="d-none d-md-flex" />
|
||||
|
||||
<v-btn
|
||||
to="/bands"
|
||||
prepend-icon="mdi-guitar-electric"
|
||||
height="100%"
|
||||
:rounded="false"
|
||||
class="d-none d-md-flex"
|
||||
>
|
||||
{{ $t('band.allBands', 2) }}
|
||||
</v-btn>
|
||||
|
||||
<v-divider vertical class="d-none d-md-flex" />
|
||||
|
||||
<v-btn
|
||||
to="/concerts"
|
||||
prepend-icon="mdi-ticket"
|
||||
height="100%"
|
||||
:rounded="false"
|
||||
class="d-none d-md-flex"
|
||||
>
|
||||
{{ $t('concert.allConcerts', 2) }}
|
||||
</v-btn>
|
||||
|
||||
<v-divider vertical class="d-none d-lg-flex" />
|
||||
|
||||
|
||||
<v-btn
|
||||
to="/locations"
|
||||
prepend-icon="mdi-city"
|
||||
height="100%"
|
||||
:rounded="false"
|
||||
class="d-none d-md-flex"
|
||||
>
|
||||
{{ $t('location.allLocations', 2) }}
|
||||
</v-btn>
|
||||
|
||||
<v-divider vertical class="d-none d-md-flex" />
|
||||
</template>
|
||||
69
src/components/navigation/urlBar.vue
Normal file
69
src/components/navigation/urlBar.vue
Normal file
@@ -0,0 +1,69 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
const router = useRouter()
|
||||
const path = ref("https://www.eventmaster.com" + router.currentRoute.value.fullPath)
|
||||
|
||||
function navigate() {
|
||||
router.replace({ path: path.value.substring(path.value.indexOf('.com') + 4) })
|
||||
}
|
||||
|
||||
watch(() => router.currentRoute.value.fullPath, () => {
|
||||
path.value = "https://www.eventmaster.com" + router.currentRoute.value.fullPath
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-app-bar
|
||||
elevation="0"
|
||||
height="50"
|
||||
>
|
||||
<v-row no-gutters>
|
||||
<v-spacer />
|
||||
<v-col
|
||||
cols="2"
|
||||
class="d-flex justify-end align-center pr-1"
|
||||
>
|
||||
<v-btn
|
||||
density="comfortable"
|
||||
icon="mdi-arrow-left"
|
||||
@click="router.go(-1)"
|
||||
/>
|
||||
<v-btn
|
||||
density="comfortable"
|
||||
icon="mdi-arrow-right"
|
||||
@click="router.go(1)"
|
||||
/>
|
||||
|
||||
</v-col>
|
||||
|
||||
<v-col cols="8">
|
||||
<v-text-field
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
hide-details
|
||||
@keyup.enter="navigate"
|
||||
v-model="path"
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<v-col
|
||||
cols="2"
|
||||
class="d-flex justify-start align-center pl-1"
|
||||
>
|
||||
<v-btn
|
||||
density="comfortable"
|
||||
icon="mdi-arrow-right"
|
||||
@click="navigate"
|
||||
/>
|
||||
<v-btn
|
||||
density="comfortable"
|
||||
icon="mdi-refresh"
|
||||
@click="router.replace({ path: router.currentRoute.value.fullPath })"
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
</v-row>
|
||||
</v-app-bar>
|
||||
</template>
|
||||
66
src/components/pageParts/bandListItem.vue
Normal file
66
src/components/pageParts/bandListItem.vue
Normal file
@@ -0,0 +1,66 @@
|
||||
<script setup lang="ts">
|
||||
import { BandModel } from '@/data/models/acts/bandModel';
|
||||
import { lowestTicketPrice } from '@/scripts/concertScripts';
|
||||
import cardViewHorizontal from '@/components/basics/cardViewHorizontal.vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { GenreModel } from '@/data/models/acts/genreModel';
|
||||
import { ConcertModel } from '@/data/models/acts/concertModel';
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
defineProps({
|
||||
/** Band to display */
|
||||
band: {
|
||||
type: BandModel,
|
||||
required: true
|
||||
},
|
||||
|
||||
concerts: {
|
||||
type: Array<ConcertModel>,
|
||||
required: true
|
||||
},
|
||||
|
||||
genres: {
|
||||
type: Array<GenreModel>,
|
||||
required: true
|
||||
},
|
||||
|
||||
/** Display text parts as skeleton */
|
||||
loading: Boolean
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<card-view-horizontal
|
||||
v-if="!loading"
|
||||
:title="band.name"
|
||||
:image="band.logo"
|
||||
@click="router.push('/bands/details/' + band.name.replaceAll(' ', '-').toLowerCase())"
|
||||
>
|
||||
<template #content>
|
||||
<div>
|
||||
<v-chip
|
||||
v-for="genre in genres"
|
||||
class="mr-2 my-1"
|
||||
variant="flat"
|
||||
>
|
||||
{{ genre.name }}
|
||||
</v-chip>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #append>
|
||||
<div>
|
||||
<div class="text-secondary font-weight-medium text-h6 pb-1">
|
||||
{{ $t('misc.from') + ' ' + lowestTicketPrice(concerts) + ' €' }}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<v-btn variant="flat" color="secondary">
|
||||
{{ $t('misc.actions.more') }}
|
||||
</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</card-view-horizontal>
|
||||
</template>
|
||||
98
src/components/pageParts/concertListItem.vue
Normal file
98
src/components/pageParts/concertListItem.vue
Normal file
@@ -0,0 +1,98 @@
|
||||
<script setup lang="ts">
|
||||
import cardViewHorizontal from '@/components/basics/cardViewHorizontal.vue';
|
||||
import { BandModel } from '@/data/models/acts/bandModel';
|
||||
import { ConcertModel } from '@/data/models/acts/concertModel';
|
||||
import { LocationModel } from '@/data/models/locations/locationModel';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
defineProps({
|
||||
/** Concert to display */
|
||||
concert: {
|
||||
type: ConcertModel,
|
||||
required: true
|
||||
},
|
||||
|
||||
band: {
|
||||
type: BandModel,
|
||||
required: true
|
||||
},
|
||||
|
||||
location: {
|
||||
type: LocationModel,
|
||||
required: true
|
||||
},
|
||||
|
||||
/** Display text parts as skeleton */
|
||||
loading: Boolean,
|
||||
|
||||
/** Show or hide the button on the right side */
|
||||
showButton: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<card-view-horizontal
|
||||
:title="concert.name"
|
||||
v-if="!loading"
|
||||
:link="showButton && concert.inStock > 0"
|
||||
@click="showButton && concert.inStock > 0 ? router.push('/concerts/booking/' + location.urlName + '/' + concert.date) : () => {}"
|
||||
>
|
||||
<template #prepend>
|
||||
<div>
|
||||
<div class="text-h4">
|
||||
{{ String(new Date(concert.date).getDate()).padStart(2, "0") }}
|
||||
</div>
|
||||
|
||||
<div class="text-h6">
|
||||
{{ new Date(concert.date).toLocaleString('default', { month: 'long' }) }}
|
||||
</div>
|
||||
|
||||
<div class="text-h6">
|
||||
{{ new Date(concert.date).getFullYear() }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #content>
|
||||
<div>
|
||||
{{ band.name }}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{{ location.name }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #append>
|
||||
<div>
|
||||
<div class="text-secondary font-weight-medium text-h6 pb-1">
|
||||
{{ $t('misc.from') + ' ' + concert.price.toFixed(2) + ' €' }}
|
||||
</div>
|
||||
|
||||
<div v-if="concert.inStock == 0 && showButton" class="text-h6">
|
||||
{{ $t('concert.concertSoldOut') }}
|
||||
</div>
|
||||
|
||||
<div v-else-if="showButton">
|
||||
<v-btn variant="flat" color="secondary">
|
||||
{{ $t('concert.goToTheConcert') }}
|
||||
</v-btn>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
</card-view-horizontal>
|
||||
|
||||
<card-view-horizontal
|
||||
v-else
|
||||
:loading="loading"
|
||||
>
|
||||
<v-skeleton-loader
|
||||
type="text" />
|
||||
</card-view-horizontal>
|
||||
</template>
|
||||
100
src/components/pageParts/heroImage.vue
Normal file
100
src/components/pageParts/heroImage.vue
Normal file
@@ -0,0 +1,100 @@
|
||||
<script setup lang="ts">
|
||||
defineProps({
|
||||
/** Background image */
|
||||
image: String,
|
||||
|
||||
/** Mini image on the left */
|
||||
logo: String,
|
||||
|
||||
/** Title */
|
||||
title: String,
|
||||
|
||||
/** Array of string to display as chips */
|
||||
chips: Array<String>,
|
||||
|
||||
/** Description text */
|
||||
description: String,
|
||||
|
||||
/** If true, display skeleton loader */
|
||||
loading: Boolean
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-img
|
||||
:src="!loading ? image : ''"
|
||||
height="600"
|
||||
gradient="to top, rgba(0, 0, 0, .9), rgba(255, 255, 255, 0.1)"
|
||||
cover
|
||||
>
|
||||
<div class="position-absolute bottom-0 pa-5" style="width: 100%;">
|
||||
<v-row>
|
||||
<!-- Logo -->
|
||||
<v-col cols="2 d-none d-md-block">
|
||||
<v-skeleton-loader
|
||||
type="image"
|
||||
:loading="loading"
|
||||
height="200"
|
||||
width="200"
|
||||
>
|
||||
<v-card>
|
||||
<v-img
|
||||
v-if="logo"
|
||||
:src="logo"
|
||||
width="200"
|
||||
aspect-ratio="1"
|
||||
cover
|
||||
/>
|
||||
</v-card>
|
||||
</v-skeleton-loader>
|
||||
</v-col>
|
||||
|
||||
|
||||
<v-col cols="8">
|
||||
<!-- Title -->
|
||||
<v-skeleton-loader
|
||||
type="heading"
|
||||
:loading="loading"
|
||||
width="500"
|
||||
>
|
||||
<span class="text-h3 font-weight-bold">
|
||||
{{ title }}
|
||||
</span>
|
||||
</v-skeleton-loader>
|
||||
|
||||
<v-skeleton-loader
|
||||
:loading="loading"
|
||||
type="sentences"
|
||||
>
|
||||
<!-- Chips -->
|
||||
<v-chip
|
||||
v-for="chip in chips"
|
||||
class="mr-2 my-1"
|
||||
variant="flat"
|
||||
>
|
||||
{{ chip }}
|
||||
</v-chip>
|
||||
|
||||
|
||||
<!-- Description -->
|
||||
<p class="text-h6 text-medium-emphasis" v-if="!$slots.description">
|
||||
{{ description }}
|
||||
</p>
|
||||
|
||||
<p class="text-h6 text-medium-emphasis">
|
||||
<slot name="description"></slot>
|
||||
</p>
|
||||
</v-skeleton-loader>
|
||||
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
</div>
|
||||
</v-img>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.v-skeleton-loader {
|
||||
background-color: rgba(0, 0, 0, 0) !important;
|
||||
}
|
||||
</style>
|
||||
29
src/components/pageParts/locationListItem.vue
Normal file
29
src/components/pageParts/locationListItem.vue
Normal file
@@ -0,0 +1,29 @@
|
||||
<script setup lang="ts">
|
||||
import cardViewTopImage from '../basics/cardViewTopImage.vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { LocationModel } from '@/data/models/locations/locationModel';
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
defineProps({
|
||||
location: {
|
||||
type: LocationModel,
|
||||
required: true
|
||||
},
|
||||
nrOfConcerts: {
|
||||
type: Number
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<card-view-top-image
|
||||
:image="location.imageOutdoor"
|
||||
:title="location.name"
|
||||
@click="router.push('locations/details/' + location.name.replaceAll(' ', '-').toLowerCase())"
|
||||
>
|
||||
<div>
|
||||
{{ nrOfConcerts }} {{ $t('concert.concert', nrOfConcerts) }}
|
||||
</div>
|
||||
</card-view-top-image>
|
||||
</template>
|
||||
32
src/components/pageParts/serverStateText.vue
Normal file
32
src/components/pageParts/serverStateText.vue
Normal file
@@ -0,0 +1,32 @@
|
||||
<script setup lang="ts">
|
||||
import { usePreferencesStore } from '@/stores/preferences.store';
|
||||
import { ServerStateEnum } from '@/data/enums/serverStateEnum';
|
||||
|
||||
const preferencesStore = usePreferencesStore()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span
|
||||
v-if="preferencesStore.serverState == ServerStateEnum.PENDING || preferencesStore.fetchInProgress"
|
||||
class="text-orange"
|
||||
>
|
||||
<v-icon icon="mdi-clock" />
|
||||
Pending...
|
||||
</span>
|
||||
|
||||
<span
|
||||
v-else-if="preferencesStore.serverState == ServerStateEnum.ONLINE"
|
||||
class="text-green"
|
||||
>
|
||||
<v-icon icon="mdi-check" />
|
||||
Online
|
||||
</span>
|
||||
|
||||
<span
|
||||
v-else="preferencesStore.serverState == ServerStateEnum.OFFLINE"
|
||||
class="text-red"
|
||||
>
|
||||
<v-icon icon="mdi-alert-circle" />
|
||||
Offline
|
||||
</span>
|
||||
</template>
|
||||
104
src/components/pageParts/ticketListItem.vue
Normal file
104
src/components/pageParts/ticketListItem.vue
Normal file
@@ -0,0 +1,104 @@
|
||||
<script setup lang="ts">
|
||||
import { ConcertModel } from '@/data/models/acts/concertModel';
|
||||
import cardWithLeftImage from '../basics/cardViewHorizontal.vue';
|
||||
import { dateStringToHumanReadableString } from '@/scripts/dateTimeScripts';
|
||||
import { BandModel } from '@/data/models/acts/bandModel';
|
||||
import { LocationModel } from '@/data/models/locations/locationModel';
|
||||
import { CityModel } from '@/data/models/locations/cityModel';
|
||||
import cardViewOneLine from '../basics/cardViewOneLine.vue';
|
||||
|
||||
defineProps({
|
||||
concert: {
|
||||
type: ConcertModel,
|
||||
required: true
|
||||
},
|
||||
|
||||
band: {
|
||||
type: BandModel,
|
||||
required: true
|
||||
},
|
||||
|
||||
location: {
|
||||
type: LocationModel,
|
||||
required: true
|
||||
},
|
||||
|
||||
city: {
|
||||
type: CityModel,
|
||||
required: true
|
||||
},
|
||||
|
||||
/** Image to print on the left side */
|
||||
image: String,
|
||||
|
||||
seatGroup: String,
|
||||
|
||||
seatRow: Number,
|
||||
|
||||
seatNr: Number
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<card-with-left-image
|
||||
:image="image"
|
||||
:link="false"
|
||||
color-header="primary"
|
||||
:title="band.name + ' - ' + concert.name"
|
||||
>
|
||||
<template #content>
|
||||
<div class="text-caption">
|
||||
{{ $t('misc.date') }}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{{ dateStringToHumanReadableString(concert.date) }}
|
||||
</div>
|
||||
|
||||
<div class="text-caption">
|
||||
{{ $t('location.location') }}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{{ location.name }}, {{ city.name }}
|
||||
</div>
|
||||
|
||||
<div class="text-caption">
|
||||
{{ $t('misc.price') }}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{{ concert.price }} €
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #append>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<card-view-one-line
|
||||
:description-text="$t('location.seat.seatGroup')"
|
||||
:value-text="seatGroup"
|
||||
/>
|
||||
|
||||
<div v-if="seatRow != 0">
|
||||
<card-view-one-line
|
||||
:description-text="$t('location.seat.seatRow')"
|
||||
:value-text="seatRow"
|
||||
/>
|
||||
|
||||
<card-view-one-line
|
||||
:description-text="$t('location.seat.seat')"
|
||||
:value-text="seatNr"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<v-div v-else>
|
||||
<card-view-one-line
|
||||
:value-text="$t('location.seat.standingArea')"
|
||||
/>
|
||||
</v-div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
</card-with-left-image>
|
||||
</template>
|
||||
155
src/components/seatPlanMap/seatGroupSheet.vue
Normal file
155
src/components/seatPlanMap/seatGroupSheet.vue
Normal file
@@ -0,0 +1,155 @@
|
||||
<script setup lang="ts">
|
||||
import { SeatGroupModel } from '@/data/models/locations/seatGroupModel';
|
||||
import seatGroupTable from './seatGroupTable.vue';
|
||||
import standingArea from './standingArea.vue';
|
||||
import { ConcertModel } from '@/data/models/acts/concertModel';
|
||||
|
||||
let props = defineProps({
|
||||
seatGroup: SeatGroupModel,
|
||||
concert: ConcertModel,
|
||||
withStage: Boolean,
|
||||
disabled: Boolean
|
||||
})
|
||||
|
||||
function getCornerClass() {
|
||||
switch(props.seatGroup.name) {
|
||||
case 'C': return "rounded-ts-xl"
|
||||
case 'E': return "rounded-bs-xl"
|
||||
case 'G': return "rounded-be-xl"
|
||||
case 'I': return "rounded-te-xl"
|
||||
}
|
||||
}
|
||||
|
||||
function getStructureNumber() {
|
||||
switch(props.seatGroup.name) {
|
||||
case 'A':
|
||||
case 'B':
|
||||
case 'F': return 0
|
||||
case 'D':
|
||||
case 'H': return 1
|
||||
case 'C': return 2
|
||||
case 'E': return 3
|
||||
case 'G': return 4
|
||||
case 'I': return 5
|
||||
}
|
||||
}
|
||||
|
||||
function getNameLocation() {
|
||||
switch(props.seatGroup.name) {
|
||||
case 'B':
|
||||
case 'C':
|
||||
case 'I': return 0
|
||||
case 'E':
|
||||
case 'F':
|
||||
case 'G': return 1
|
||||
case 'D': return 2
|
||||
case 'H': return 3
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<standing-area
|
||||
:seat-group="seatGroup"
|
||||
:concert="concert"
|
||||
:with-stage="withStage"
|
||||
v-if="seatGroup != undefined && seatGroup.standingArea"
|
||||
:disabled="disabled"
|
||||
/>
|
||||
|
||||
<v-sheet
|
||||
v-else-if="seatGroup != undefined"
|
||||
class="pa-2"
|
||||
:class="getCornerClass()"
|
||||
border
|
||||
style="height: 100%;"
|
||||
>
|
||||
<!-- Block name above seat icons -->
|
||||
<div v-if="getNameLocation() == 0">
|
||||
<v-row >
|
||||
<v-col class="text-h4 text-center font-weight-black">
|
||||
{{ seatGroup.name }}
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row>
|
||||
<v-spacer v-if="seatGroup.name == 'B' || seatGroup.name == 'C'" />
|
||||
|
||||
<v-col cols="auto">
|
||||
<seat-group-table
|
||||
:seat-rows="seatGroup.seatRows"
|
||||
:seat-group="seatGroup"
|
||||
:concert="concert"
|
||||
:structure="getStructureNumber()"
|
||||
:disabled="disabled"
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<v-spacer v-if="seatGroup.name == 'B' || seatGroup.name == 'I'" />
|
||||
</v-row>
|
||||
</div>
|
||||
|
||||
<!-- Block name under seat icons -->
|
||||
<div v-else-if="getNameLocation() == 1">
|
||||
<v-row>
|
||||
<v-col class="d-flex justify-center align-center">
|
||||
<v-spacer v-if="seatGroup.name == 'E'" />
|
||||
|
||||
<seat-group-table
|
||||
:seat-rows="seatGroup.seatRows"
|
||||
:seat-group="seatGroup"
|
||||
:concert="concert"
|
||||
:structure="getStructureNumber()"
|
||||
:disabled="disabled"
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<v-spacer v-if="seatGroup.name == 'G'" />
|
||||
</v-row>
|
||||
|
||||
<v-row >
|
||||
<v-col class="text-h4 text-center font-weight-black">
|
||||
{{ seatGroup.name }}
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
|
||||
<!-- Block name left to seat icons -->
|
||||
<div v-else-if="getNameLocation() == 2">
|
||||
<v-row>
|
||||
<v-col class="text-h4 font-weight-black d-flex justify-center align-center">
|
||||
{{ seatGroup.name }}
|
||||
</v-col>
|
||||
|
||||
<v-col class="d-flex justify-center align-center">
|
||||
<seat-group-table
|
||||
:seat-rows="seatGroup.seatRows"
|
||||
:seat-group="seatGroup"
|
||||
:concert="concert"
|
||||
:structure="getStructureNumber()"
|
||||
:disabled="disabled"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
|
||||
<!-- Block name right to seat icons -->
|
||||
<div v-else-if="getNameLocation() == 3">
|
||||
<v-row>
|
||||
<v-col class="d-flex justify-center align-center">
|
||||
<seat-group-table
|
||||
:seat-rows="seatGroup.seatRows"
|
||||
:seat-group="seatGroup"
|
||||
:concert="concert"
|
||||
:structure="getStructureNumber()"
|
||||
:disabled="disabled"
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<v-col class="text-h4 font-weight-black d-flex justify-center align-center">
|
||||
{{ seatGroup.name }}
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
</v-sheet>
|
||||
</template>
|
||||
165
src/components/seatPlanMap/seatGroupTable.vue
Normal file
165
src/components/seatPlanMap/seatGroupTable.vue
Normal file
@@ -0,0 +1,165 @@
|
||||
<script setup lang="ts">
|
||||
import { ConcertModel } from '@/data/models/acts/concertModel';
|
||||
import { SeatGroupModel } from '@/data/models/locations/seatGroupModel';
|
||||
import { SeatModel } from '@/data/models/locations/seatModel';
|
||||
import { SeatRowModel } from '@/data/models/locations/seatRowModel';
|
||||
import { SelectedSeatModel } from '@/data/models/ordering/selectedSeatModel';
|
||||
import { getSeatColor } from '@/scripts/colorScripts';
|
||||
import { useBasketStore } from '@/stores/basket.store';
|
||||
|
||||
const basketStore = useBasketStore()
|
||||
|
||||
let props = defineProps({
|
||||
seatRows: Array<SeatRowModel>,
|
||||
concert: ConcertModel,
|
||||
seatGroup: SeatGroupModel,
|
||||
|
||||
/** Seat is selectable (false) or not (true) */
|
||||
disabled: Boolean,
|
||||
|
||||
/**
|
||||
* Structure of the seat placment
|
||||
*
|
||||
* 0 = Normal rectangular form, rows from top to bottom (default)
|
||||
* 1 = Normal rectangular form but rows and cols are switched
|
||||
* 2 = Pyramid structure from right bottom corner
|
||||
* 3 = Pyramid structure from right top corner
|
||||
* 4 = Pyramid structure from left top corner
|
||||
* 5 = Pyramid structure from left bottom corner
|
||||
*/
|
||||
structure: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Moves seat to store and changes the state
|
||||
*
|
||||
* @param clickedSeat Model which is clicked
|
||||
* @param seatRow Row of the seat
|
||||
*/
|
||||
function handleSeatClick(clickedSeat: SeatModel, seatRow: SeatRowModel) {
|
||||
let storeSeat = basketStore.selectedSeats.find(seat =>
|
||||
seat.seat.id == clickedSeat.id
|
||||
)
|
||||
|
||||
if (storeSeat == undefined) {
|
||||
clickedSeat.state = 2
|
||||
basketStore.selectedSeats.push(new SelectedSeatModel(clickedSeat, seatRow.row, props.seatGroup.name, props.concert))
|
||||
} else {
|
||||
clickedSeat.state = 0
|
||||
basketStore.selectedSeats = basketStore.selectedSeats.filter(seat =>
|
||||
seat.seat.id != clickedSeat.id
|
||||
)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<table v-if="structure == 0">
|
||||
<tbody>
|
||||
<tr v-for="seatRow in seatRows">
|
||||
<td v-for="seat in seatRow.seats">
|
||||
<v-btn
|
||||
variant="text"
|
||||
icon="mdi-circle"
|
||||
:color="getSeatColor(seatGroup.surcharge, seat.state)"
|
||||
:disabled="seat.state == 1"
|
||||
density="compact"
|
||||
@click="() => !disabled ? handleSeatClick(seat, seatRow) : {}"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<table v-else-if="structure == 1">
|
||||
<tbody>
|
||||
<tr v-for="i in seatRows[0].seats.length">
|
||||
<td v-for="row in seatRows">
|
||||
<v-btn
|
||||
variant="text"
|
||||
icon="mdi-circle"
|
||||
:color="getSeatColor(seatGroup.surcharge, row.seats[i - 1].state)"
|
||||
:disabled="row.seats[i - 1].state == 1"
|
||||
density="compact"
|
||||
@click="() => !disabled ? handleSeatClick(row.seats[i - 1], row) : {}"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<table v-else-if="structure == 2">
|
||||
<tbody>
|
||||
<tr v-for="i in seatRows.length">
|
||||
<td v-for="j in seatRows[i - 1].seats.length">
|
||||
<v-btn
|
||||
v-if="seatRows[i - 1].seats.length - i < j"
|
||||
variant="text"
|
||||
icon="mdi-circle"
|
||||
:color="getSeatColor(seatGroup.surcharge, seatRows[i - 1].seats[j - 1].state)"
|
||||
:disabled="seatRows[i - 1].seats[j - 1].state == 1"
|
||||
density="compact"
|
||||
@click="() => !disabled ? handleSeatClick(seatRows[i - 1].seats[j - 1], seatRows[i - 1]) : {}"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<table v-else-if="structure == 3">
|
||||
<tbody>
|
||||
<tr v-for="i in seatRows.length">
|
||||
<td v-for="j in seatRows[i - 1].seats.length">
|
||||
<v-btn
|
||||
v-if="j >= i"
|
||||
variant="text"
|
||||
icon="mdi-circle"
|
||||
:color="getSeatColor(seatGroup.surcharge, seatRows[i - 1].seats[j - 1].state)"
|
||||
:disabled="seatRows[i - 1].seats[j - 1].state == 1"
|
||||
density="compact"
|
||||
@click="() => !disabled ? handleSeatClick(seatRows[i - 1].seats[j - 1], seatRows[i - 1]) : {}"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<table v-else-if="structure == 4">
|
||||
<tbody>
|
||||
<tr v-for="i in seatRows.length">
|
||||
<td v-for="j in seatRows[i - 1].seats.length">
|
||||
<v-btn
|
||||
v-if="seatRows[i - 1].seats.length - i >= j - 1"
|
||||
variant="text"
|
||||
icon="mdi-circle"
|
||||
:color="getSeatColor(seatGroup.surcharge, seatRows[i - 1].seats[j - 1].state)"
|
||||
:disabled="seatRows[i - 1].seats[j - 1].state == 1"
|
||||
density="compact"
|
||||
@click="() => !disabled ? handleSeatClick(seatRows[i - 1].seats[j - 1], seatRows[i - 1]) : {}"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<table v-else-if="structure == 5">
|
||||
<tbody>
|
||||
<tr v-for="i in seatRows.length">
|
||||
<td v-for="j in seatRows[i - 1].seats.length">
|
||||
<v-btn
|
||||
v-if="j <= i"
|
||||
variant="text"
|
||||
icon="mdi-circle"
|
||||
:color="getSeatColor(seatGroup.surcharge, seatRows[i - 1].seats[j - 1].state)"
|
||||
:disabled="seatRows[i - 1].seats[j - 1].state == 1"
|
||||
density="compact"
|
||||
@click="() => !disabled ? handleSeatClick(seatRows[i - 1].seats[j - 1], seatRows[i - 1]) : {}"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
150
src/components/seatPlanMap/seatPlanMap.vue
Normal file
150
src/components/seatPlanMap/seatPlanMap.vue
Normal file
@@ -0,0 +1,150 @@
|
||||
<script setup lang="ts">
|
||||
import { SeatGroupModel } from '@/data/models/locations/seatGroupModel';
|
||||
import seatGroupSheet from './seatGroupSheet.vue';
|
||||
import { ConcertModel } from '@/data/models/acts/concertModel';
|
||||
import { LocationModel } from '@/data/models/locations/locationModel';
|
||||
|
||||
let props = defineProps({
|
||||
location: LocationModel,
|
||||
seatGroups: {
|
||||
type: Array<SeatGroupModel>,
|
||||
required: true
|
||||
},
|
||||
concert: {
|
||||
type: ConcertModel,
|
||||
default: new ConcertModel()
|
||||
},
|
||||
disabled: Boolean
|
||||
})
|
||||
|
||||
function findSeatCategory(name: string): SeatGroupModel {
|
||||
return props.seatGroups.find(category =>
|
||||
category.name == name
|
||||
)
|
||||
}
|
||||
|
||||
const seatGroupA = findSeatCategory("A")
|
||||
const seatGroupB = findSeatCategory("B")
|
||||
const seatGroupC = findSeatCategory("C")
|
||||
const seatGroupD = findSeatCategory("D")
|
||||
const seatGroupE = findSeatCategory("E")
|
||||
const seatGroupF = findSeatCategory("F")
|
||||
const seatGroupG = findSeatCategory("G")
|
||||
const seatGroupH = findSeatCategory("H")
|
||||
const seatGroupI = findSeatCategory("I")
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-sheet border class="pa-5">
|
||||
<v-row>
|
||||
<!-- Seat Group C -->
|
||||
<v-col cols="4" class="pa-0">
|
||||
<seat-group-sheet
|
||||
:seat-group="seatGroupC"
|
||||
:concert="concert"
|
||||
background-color="grey"
|
||||
:disabled="disabled"
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<!-- Seat Group B -->
|
||||
<v-col cols="4" class="pa-0">
|
||||
<seat-group-sheet
|
||||
:seat-group="seatGroupB"
|
||||
:concert="concert"
|
||||
background-color="grey"
|
||||
:disabled="disabled"
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<!-- Seat Group I -->
|
||||
<v-col cols="4" class="pa-0">
|
||||
<seat-group-sheet
|
||||
:seat-group="seatGroupI"
|
||||
:concert="concert"
|
||||
background-color="grey"
|
||||
:disabled="disabled"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row>
|
||||
<!-- Seat Group D -->
|
||||
<v-col cols="4" class="pa-0">
|
||||
<seat-group-sheet
|
||||
v-if="location.layout != 1"
|
||||
:seat-group="seatGroupD"
|
||||
:concert="concert"
|
||||
background-color="grey"
|
||||
:disabled="disabled"
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<!-- Seat Group A -->
|
||||
<v-col :cols="location.layout == 1 ? 10 : 4" class="pa-0">
|
||||
<seat-group-sheet
|
||||
:seat-group="seatGroupA"
|
||||
:concert="concert"
|
||||
background-color="grey"
|
||||
:withStage="location.layout == 3"
|
||||
:disabled="disabled"
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<!-- Stage of layout != 3 -->
|
||||
<v-col v-if="location.layout != 3">
|
||||
<v-sheet
|
||||
color="grey-darken-3"
|
||||
height="100%"
|
||||
width="50"
|
||||
class="px-5 py-2 d-flex justify-center align-center"
|
||||
>
|
||||
{{ $t('location.stage') }}
|
||||
</v-sheet>
|
||||
</v-col>
|
||||
|
||||
<!-- Seat Group H if layout == 3 -->
|
||||
<v-col v-else cols="4" class="pa-0">
|
||||
<seat-group-sheet
|
||||
:seat-group="seatGroupH"
|
||||
:concert="concert"
|
||||
background-color="grey"
|
||||
:disabled="disabled"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
|
||||
<v-row v-if="location.layout != 1">
|
||||
<!-- Seat Group E -->
|
||||
<v-col cols="4" class="pa-0">
|
||||
<seat-group-sheet
|
||||
:seat-group="seatGroupE"
|
||||
:concert="concert"
|
||||
background-color="grey"
|
||||
:disabled="disabled"
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<!-- Seat Group F -->
|
||||
<v-col cols="4" class="pa-0">
|
||||
<seat-group-sheet
|
||||
:seat-group="seatGroupF"
|
||||
:concert="concert"
|
||||
background-color="grey"
|
||||
:disabled="disabled"
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<!-- Seat Group G -->
|
||||
<v-col cols="4" class="pa-0">
|
||||
<seat-group-sheet
|
||||
:seat-group="seatGroupG"
|
||||
:concert="concert"
|
||||
background-color="grey"
|
||||
:disabled="disabled"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-sheet>
|
||||
</template>
|
||||
84
src/components/seatPlanMap/standingArea.vue
Normal file
84
src/components/seatPlanMap/standingArea.vue
Normal file
@@ -0,0 +1,84 @@
|
||||
<script setup lang="ts">
|
||||
import { ConcertModel } from '@/data/models/acts/concertModel';
|
||||
import { SeatGroupModel } from '@/data/models/locations/seatGroupModel';
|
||||
import { SelectedSeatModel } from '@/data/models/ordering/selectedSeatModel';
|
||||
import { useBasketStore } from '@/stores/basket.store';
|
||||
|
||||
const basketStore = useBasketStore()
|
||||
|
||||
let props = defineProps({
|
||||
seatGroup: SeatGroupModel,
|
||||
concert: ConcertModel,
|
||||
backgroundColor: String,
|
||||
withStage: Boolean,
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
function handleSeatClick() {
|
||||
let freeSeat = props.seatGroup.seatRows[0].seats.find(seat =>
|
||||
seat.state == 0
|
||||
)
|
||||
|
||||
freeSeat.state = 2
|
||||
|
||||
basketStore.selectedSeats.push(new SelectedSeatModel(freeSeat, 0, props.seatGroup.name, props.concert))
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-hover>
|
||||
<template v-slot:default="{ isHovering, props}">
|
||||
<v-sheet
|
||||
v-bind="props"
|
||||
class="pa-5"
|
||||
min-height="200"
|
||||
height="100%"
|
||||
border
|
||||
:color="isHovering ? 'orange' : ''"
|
||||
@click="() => { !disabled ? handleSeatClick() : {} }"
|
||||
>
|
||||
<v-row class="d-flex justify-center align-center h-100">
|
||||
<v-col>
|
||||
<v-row>
|
||||
<v-spacer />
|
||||
|
||||
<v-col class="text-center" cols="6">
|
||||
<v-icon
|
||||
v-if="!withStage"
|
||||
icon="mdi-account-group"
|
||||
size="x-large"
|
||||
/>
|
||||
|
||||
<v-sheet
|
||||
v-else
|
||||
color="grey-darken-3"
|
||||
height="100"
|
||||
width="100%"
|
||||
|
||||
>
|
||||
{{ $t('location.stage') }}
|
||||
</v-sheet>
|
||||
</v-col>
|
||||
|
||||
<v-spacer />
|
||||
</v-row>
|
||||
|
||||
<v-row>
|
||||
<v-col class="text-center text-h6">
|
||||
{{ seatGroup.occupied != undefined
|
||||
? seatGroup.capacity - seatGroup.occupied
|
||||
: seatGroup.capacity
|
||||
}}
|
||||
{{ $t('location.seat.standingPlace', 2) }}
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-sheet>
|
||||
</template>
|
||||
</v-hover>
|
||||
</template>
|
||||
36
src/data/api/accountApi.ts
Normal file
36
src/data/api/accountApi.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import axios from "axios"
|
||||
import { AccountModel } from "../models/user/accountModel"
|
||||
|
||||
const BASE_URL = "http://localhost:3000/accounts"
|
||||
|
||||
export async function fetchAllAccounts() {
|
||||
return await axios.get(BASE_URL)
|
||||
}
|
||||
|
||||
export async function login(username: string, password: string) {
|
||||
return await axios.get(BASE_URL + "/login?username=" + username + "&password=" + password)
|
||||
}
|
||||
|
||||
export async function getAccount(token: string) {
|
||||
return await axios.get(BASE_URL + "/account", {
|
||||
headers: {
|
||||
"Authorization": token
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export async function registerAccount(account: AccountModel) {
|
||||
return await axios.post(BASE_URL, account)
|
||||
}
|
||||
|
||||
export async function updateAccount(account: AccountModel, token: string) {
|
||||
return await axios.patch(BASE_URL, account, {
|
||||
headers: {
|
||||
"Authorization": token
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export async function deleteAccount(account: AccountModel) {
|
||||
return await axios.delete(BASE_URL + "/" + account.id)
|
||||
}
|
||||
25
src/data/api/bandApi.ts
Normal file
25
src/data/api/bandApi.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import axios from "axios"
|
||||
import { BandDetailsApiModel } from "../models/acts/bandDetailsApiModel"
|
||||
import { BandModel } from "../models/acts/bandModel"
|
||||
|
||||
let BASE_URL = "http://localhost:3000/bands"
|
||||
|
||||
export async function fetchAllBands() {
|
||||
return await axios.get(BASE_URL)
|
||||
}
|
||||
|
||||
export async function fetchBandByName(bandName: string) {
|
||||
return await axios.get(BASE_URL + '/band/' + bandName)
|
||||
}
|
||||
|
||||
export async function fetchBandsBySearchTerm(searchTerm: string) {
|
||||
return await axios.get(BASE_URL + '/search?value=' + searchTerm)
|
||||
}
|
||||
|
||||
export async function postBand(band: BandModel) {
|
||||
return await axios.post(BASE_URL, band)
|
||||
}
|
||||
|
||||
export async function patchBand(band: BandModel) {
|
||||
return await axios.patch(BASE_URL, band)
|
||||
}
|
||||
7
src/data/api/cityApi.ts
Normal file
7
src/data/api/cityApi.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import axios from "axios"
|
||||
|
||||
const BASE_URL = "http://localhost:3000/cities"
|
||||
|
||||
export async function fetchAllCities() {
|
||||
return await axios.get(BASE_URL)
|
||||
}
|
||||
32
src/data/api/concertApi.ts
Normal file
32
src/data/api/concertApi.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import axios from "axios"
|
||||
|
||||
let BASE_URL = "http://localhost:3000/concerts"
|
||||
|
||||
export async function fetchAllConcerts() {
|
||||
return await axios.get(BASE_URL)
|
||||
}
|
||||
|
||||
export async function fetchConcertById(id: number) {
|
||||
if (id != undefined) {
|
||||
return await axios.get(BASE_URL + "/concert/" + id)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns events with the most concerts
|
||||
*
|
||||
* @param nrOfConcerts Limit number of returned objects
|
||||
*
|
||||
* @returns Limited number of objects with the most concerts in it
|
||||
*/
|
||||
export async function fetchUpcomingConcerts(nrOfConcerts: number) {
|
||||
let url = BASE_URL + "?count=" + nrOfConcerts
|
||||
|
||||
return await axios.get(url)
|
||||
}
|
||||
|
||||
export async function fetchConcertsBySearchTerm(searchTerm: string) {
|
||||
return await axios.get(BASE_URL + '/search?value=' + searchTerm)
|
||||
}
|
||||
13
src/data/api/exerciseApi.ts
Normal file
13
src/data/api/exerciseApi.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import axios from "axios"
|
||||
|
||||
const BASE_URL = "http://localhost:3000/exercises"
|
||||
|
||||
export async function fetchAllExerciseGroups() {
|
||||
return await axios.get(BASE_URL)
|
||||
}
|
||||
|
||||
export async function updateExercise(exerciseGroupNr: number, exerciseNr: number, state: boolean) {
|
||||
let url = BASE_URL + "/" + exerciseGroupNr + "/" + exerciseNr + "/" + (state ? "1" : "0")
|
||||
|
||||
return await axios.post(url)
|
||||
}
|
||||
45
src/data/api/files.api.ts
Normal file
45
src/data/api/files.api.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import axios from "axios"
|
||||
|
||||
const BASE_URL = "http://localhost:3000/files"
|
||||
|
||||
/**
|
||||
* Fetch all public folders on server
|
||||
*
|
||||
* @returns Response from server
|
||||
*/
|
||||
export function fetchFolderNames() {
|
||||
return axios.get(BASE_URL + "/folders")
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all static file names
|
||||
*
|
||||
* @param dirName Name of folder where to scan files
|
||||
*
|
||||
* @returns Response from server
|
||||
*/
|
||||
export function fetchFileNames(dirName: string) {
|
||||
return axios.get(BASE_URL + "/" + dirName)
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload a file to the server
|
||||
*
|
||||
* @param file File to store on server
|
||||
*
|
||||
* @returns Response from server
|
||||
*/
|
||||
export function postFile(file, folder: string) {
|
||||
let formData = new FormData()
|
||||
|
||||
formData.append("file", file)
|
||||
formData.append("folder", folder)
|
||||
|
||||
console.log(formData)
|
||||
|
||||
return axios.post(BASE_URL, formData, {
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data"
|
||||
}
|
||||
})
|
||||
}
|
||||
22
src/data/api/genreApi.ts
Normal file
22
src/data/api/genreApi.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import axios from "axios"
|
||||
import { GenreModel } from "../models/acts/genreModel"
|
||||
|
||||
let BASE_URL = "http://localhost:3000/genres"
|
||||
|
||||
export async function fetchAllGenres() {
|
||||
return await axios.get(BASE_URL)
|
||||
}
|
||||
|
||||
export async function postGenre(genre: GenreModel) {
|
||||
return await axios.post(BASE_URL, genre)
|
||||
}
|
||||
|
||||
export async function patchGenre(genre: GenreModel) {
|
||||
return await axios.patch(BASE_URL, genre)
|
||||
}
|
||||
|
||||
export async function deleteGenre(genre: GenreModel) {
|
||||
return await axios.delete(BASE_URL, {
|
||||
data: genre
|
||||
})
|
||||
}
|
||||
21
src/data/api/locationApi.ts
Normal file
21
src/data/api/locationApi.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import axios from "axios"
|
||||
|
||||
const BASE_URL = "http://localhost:3000/locations"
|
||||
|
||||
export async function fetchAllLocations() {
|
||||
return await axios.get(BASE_URL)
|
||||
}
|
||||
|
||||
export async function fetchLocationByName(locationName: string) {
|
||||
return await axios.get(BASE_URL + "/location/" + locationName)
|
||||
}
|
||||
|
||||
export async function fetchTopLocations(nrOfLocations: number) {
|
||||
let url = BASE_URL + "?sort=desc&count=" + nrOfLocations
|
||||
|
||||
return await axios.get(url)
|
||||
}
|
||||
|
||||
export async function fetchLocationsBySearchTerm(searchTerm: string) {
|
||||
return await axios.get(BASE_URL + "/search?value=" + searchTerm)
|
||||
}
|
||||
30
src/data/api/mainApi.ts
Normal file
30
src/data/api/mainApi.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import axios from "axios"
|
||||
|
||||
const BASE_URL = "http://localhost:3000/api"
|
||||
|
||||
/**
|
||||
* Fetch the current state of backend server
|
||||
*
|
||||
* @returns Response from server
|
||||
*/
|
||||
export function fetchServerState() {
|
||||
return axios.get(BASE_URL)
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the database (without exercise progress) to factory state
|
||||
*
|
||||
* @returns Response from server
|
||||
*/
|
||||
export function resetDatabase() {
|
||||
return axios.get(BASE_URL + "/resetdatabase")
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the exercise progress
|
||||
*
|
||||
* @returns Response from server
|
||||
*/
|
||||
export function resetExerciseProgress() {
|
||||
return axios.get(BASE_URL + "/resetExerciseProgress")
|
||||
}
|
||||
45
src/data/api/orderApi.ts
Normal file
45
src/data/api/orderApi.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import axios from "axios"
|
||||
import { BasketItemModel } from "../models/ordering/basketItemModel"
|
||||
|
||||
const BASE_URL = "http://localhost:3000/orders"
|
||||
|
||||
export async function fetchUserOrders(userId: number) {
|
||||
return axios.get(BASE_URL + "/" + userId)
|
||||
}
|
||||
|
||||
export async function createOrder(
|
||||
accountId: number,
|
||||
basketItem: Array<BasketItemModel>,
|
||||
paymentId: number,
|
||||
addressId: number
|
||||
) {
|
||||
let tickets = []
|
||||
|
||||
for (let item of basketItem) {
|
||||
for (let seat of item.seats) {
|
||||
tickets.push({
|
||||
concertId: item.concert.id,
|
||||
orderPrice: item.price,
|
||||
seatId: seat.id
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
console.log({
|
||||
accountId: accountId,
|
||||
tickets: tickets,
|
||||
paymentId: paymentId,
|
||||
addressId: addressId
|
||||
})
|
||||
|
||||
return axios.post(BASE_URL, {
|
||||
accountId: accountId,
|
||||
tickets: tickets,
|
||||
paymentId: paymentId,
|
||||
addressId: addressId
|
||||
})
|
||||
}
|
||||
|
||||
export async function fetchAllOrders() {
|
||||
return axios.get(BASE_URL)
|
||||
}
|
||||
100
src/data/enums/bannerStateEnum.ts
Normal file
100
src/data/enums/bannerStateEnum.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
export enum BannerStateEnum {
|
||||
////////// System feedback //////////
|
||||
|
||||
// Some error
|
||||
ERROR,
|
||||
|
||||
BASKETPRODUCTADDED,
|
||||
|
||||
BASKETPRODUCTREMOVED,
|
||||
|
||||
|
||||
////////// Exercise feedback //////////
|
||||
|
||||
EXERCISESOLVED01,
|
||||
|
||||
EXERCISESOLVED02,
|
||||
|
||||
EXERCISESOLVED03,
|
||||
|
||||
EXERCISESOLVED11,
|
||||
|
||||
EXERCISESOLVED12,
|
||||
|
||||
EXERCISESOLVED13,
|
||||
|
||||
EXERCISESOLVED21,
|
||||
|
||||
EXERCISESOLVED22,
|
||||
|
||||
EXERCISESOLVED23,
|
||||
|
||||
EXERCISESOLVED31,
|
||||
|
||||
EXERCISESOLVED32,
|
||||
|
||||
EXERCISESOLVED33,
|
||||
|
||||
|
||||
////////// API Endpoint /api //////////
|
||||
|
||||
// Status: 200 OK
|
||||
DATABASERESETSUCCESSFUL,
|
||||
|
||||
// Status: 200 OK
|
||||
EXERCISEPROGRESSRESETSUCCESSFUL,
|
||||
|
||||
|
||||
////////// API Endpoint /accounts //////////
|
||||
|
||||
// Status: 200 OK
|
||||
ACCOUNTLOGINSUCCESSFUL,
|
||||
|
||||
// Status: 400 Bad request
|
||||
ACCOUNTLOGINERROR,
|
||||
|
||||
// Status: 401 Unauthorized
|
||||
ACCOUNTLOGINWRONGLOGIN,
|
||||
|
||||
// Status: 201 Created
|
||||
ACCOUNTREGISTERSUCCESSFUL,
|
||||
|
||||
// Status: 400 Bad request
|
||||
ACCOUNTREGISTERERROR,
|
||||
|
||||
// Status: 409 Conflict
|
||||
ACCOUNTREGISTERUSERNAMEINUSE,
|
||||
|
||||
// Status: 200 OK
|
||||
ACCOUNTUPDATESUCCESSFUL,
|
||||
|
||||
// No status code, runs in local cache
|
||||
ACCOUNTLOGOUTSUCCESSFUL,
|
||||
|
||||
|
||||
////////// API Endpoint /orders //////////
|
||||
|
||||
// Status: 201 Created
|
||||
ORDERPLACESUCCESSFUL,
|
||||
|
||||
|
||||
////////// API Endpoint /bands //////////
|
||||
|
||||
BANDSAVEDSUCCESSFUL,
|
||||
|
||||
BANDSAVEDERROR,
|
||||
|
||||
BANDDELETESUCCESSFUL,
|
||||
|
||||
BANDDELETEERROR,
|
||||
|
||||
////////// API Endpoint /genres //////////
|
||||
|
||||
GENRESAVEDSUCCESSFUL,
|
||||
|
||||
GENRESAVEDERROR,
|
||||
|
||||
GENREDELETESUCCESSFUL,
|
||||
|
||||
GENREDELETEERROR
|
||||
}
|
||||
4
src/data/enums/languageEnum.ts
Normal file
4
src/data/enums/languageEnum.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export enum LanguageEnum {
|
||||
GERMAN = "de",
|
||||
ENGLISH = "en"
|
||||
}
|
||||
5
src/data/enums/serverStateEnum.ts
Normal file
5
src/data/enums/serverStateEnum.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export enum ServerStateEnum {
|
||||
ONLINE,
|
||||
OFFLINE,
|
||||
PENDING
|
||||
}
|
||||
6
src/data/enums/sortOrderEnum.ts
Normal file
6
src/data/enums/sortOrderEnum.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export enum SortOrder {
|
||||
PRICELOWTOHIGH = "Price: Low to high",
|
||||
PRICEHIGHTOLOW = "Price: High to low",
|
||||
NAMEATOZ = "Name: A to Z",
|
||||
NAMEZTOA = "Name: Z to A"
|
||||
}
|
||||
4
src/data/enums/themeEnums.ts
Normal file
4
src/data/enums/themeEnums.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export enum ThemeEnum {
|
||||
DARK = "dark",
|
||||
LIGHT = "light",
|
||||
}
|
||||
14
src/data/models/acts/bandApiModel.ts
Normal file
14
src/data/models/acts/bandApiModel.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { BandModel } from "./bandModel";
|
||||
import { ConcertModel } from "./concertModel";
|
||||
import { GenreModel } from "./genreModel"
|
||||
import { MemberModel } from "./memberModel";
|
||||
|
||||
/**
|
||||
* Replica of the API endpoint /bands
|
||||
*/
|
||||
export class BandApiModel extends BandModel {
|
||||
members: Array<MemberModel>
|
||||
genres: Array<GenreModel> = []
|
||||
rating: number = 0
|
||||
concerts: Array<ConcertModel> = []
|
||||
}
|
||||
15
src/data/models/acts/bandDetailsApiModel.ts
Normal file
15
src/data/models/acts/bandDetailsApiModel.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { BandModel } from "./bandModel";
|
||||
import { ConcertApiModel } from "./concertApiModel";
|
||||
import { GenreModel } from "./genreModel"
|
||||
import { MemberModel } from "./memberModel";
|
||||
import { RatingModel } from "./ratingModel"
|
||||
|
||||
/**
|
||||
* Replica of the API endpoint /bands/band/:name
|
||||
*/
|
||||
export class BandDetailsApiModel extends BandModel {
|
||||
members: Array<MemberModel> = []
|
||||
ratingValues: Array<RatingModel> = []
|
||||
genres: Array<GenreModel> = []
|
||||
concerts: Array<ConcertApiModel> = []
|
||||
}
|
||||
11
src/data/models/acts/bandModel.ts
Normal file
11
src/data/models/acts/bandModel.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export class BandModel {
|
||||
id: number
|
||||
name: string = ""
|
||||
foundingYear: number = 1900
|
||||
descriptionEn: string = ""
|
||||
descriptionDe: string = ""
|
||||
images: Array<string> = []
|
||||
imageMembers: string = ""
|
||||
logo: string = ""
|
||||
rating: number = 0
|
||||
}
|
||||
8
src/data/models/acts/concertApiModel.ts
Normal file
8
src/data/models/acts/concertApiModel.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { LocationApiModel } from "../locations/locationApiModel"
|
||||
import { BandModel } from "./bandModel"
|
||||
import { ConcertModel } from "./concertModel"
|
||||
|
||||
export class ConcertApiModel extends ConcertModel {
|
||||
location: LocationApiModel = new LocationApiModel()
|
||||
band: BandModel = new BandModel()
|
||||
}
|
||||
8
src/data/models/acts/concertDetailsApiModel.ts
Normal file
8
src/data/models/acts/concertDetailsApiModel.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { LocationDetailsApiModel } from "../locations/locationDetailsApiModel";
|
||||
import { BandModel } from "./bandModel";
|
||||
import { ConcertModel } from "./concertModel";
|
||||
|
||||
export class ConcertDetailsApiModel extends ConcertModel {
|
||||
location: LocationDetailsApiModel = new LocationDetailsApiModel()
|
||||
band: BandModel = new BandModel()
|
||||
}
|
||||
9
src/data/models/acts/concertModel.ts
Normal file
9
src/data/models/acts/concertModel.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export class ConcertModel {
|
||||
id: number = -1
|
||||
date: string = ""
|
||||
name: string = ""
|
||||
price: number = 0
|
||||
image: string = ""
|
||||
inStock: number = 0
|
||||
offered: boolean = true
|
||||
}
|
||||
6
src/data/models/acts/genreApiModel.ts
Normal file
6
src/data/models/acts/genreApiModel.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { GenreModel } from "./genreModel";
|
||||
import { BandModel } from "./bandModel"
|
||||
|
||||
export class GenreApiModel extends GenreModel {
|
||||
bands: Array<BandModel>
|
||||
}
|
||||
4
src/data/models/acts/genreModel.ts
Normal file
4
src/data/models/acts/genreModel.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export class GenreModel {
|
||||
id: number
|
||||
name: string = ""
|
||||
}
|
||||
4
src/data/models/acts/memberModel.ts
Normal file
4
src/data/models/acts/memberModel.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export class MemberModel {
|
||||
name: string = ""
|
||||
image: string = ""
|
||||
}
|
||||
4
src/data/models/acts/ratingModel.ts
Normal file
4
src/data/models/acts/ratingModel.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export class RatingModel {
|
||||
value: number = 0
|
||||
count: number = 0
|
||||
}
|
||||
32
src/data/models/apiEndpoints/orderApiModel.ts
Normal file
32
src/data/models/apiEndpoints/orderApiModel.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { AccountModel } from "../user/accountModel"
|
||||
import { AddressModel } from "../user/addressModel"
|
||||
import { PaymentModel } from "../user/paymentModel"
|
||||
import { OrderModel } from "../ordering/orderModel"
|
||||
import { TicketModel } from "../ordering/ticketModel"
|
||||
import { ConcertApiModel } from "../acts/concertApiModel"
|
||||
import { SeatModel } from "../locations/seatModel"
|
||||
import { SeatRowModel } from "../locations/seatRowModel"
|
||||
import { SeatGroupModel } from "../locations/seatGroupModel"
|
||||
|
||||
/**
|
||||
* Replica of API endpoint /orders/:id
|
||||
*/
|
||||
export class OrderApiModel extends OrderModel {
|
||||
tickets: Array<TicketOrderModel>
|
||||
account: AccountModel
|
||||
payment: PaymentModel
|
||||
address: AddressModel
|
||||
}
|
||||
|
||||
class TicketOrderModel extends TicketModel {
|
||||
concert: ConcertApiModel
|
||||
seat: SeatTicketModel
|
||||
}
|
||||
|
||||
class SeatTicketModel extends SeatModel {
|
||||
seatRow: SeatRowTicketModel
|
||||
}
|
||||
|
||||
class SeatRowTicketModel extends SeatRowModel {
|
||||
seatGroup: SeatGroupModel
|
||||
}
|
||||
8
src/data/models/exercises/exerciseGroupModel.ts
Normal file
8
src/data/models/exercises/exerciseGroupModel.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export class ExerciseGroupModel {
|
||||
id = -1
|
||||
nameDe: string = ""
|
||||
nameEn: string = ""
|
||||
groupNr: number = 0
|
||||
descriptionDe: string = ""
|
||||
descriptionEn: string = ""
|
||||
}
|
||||
12
src/data/models/exercises/exerciseModel.ts
Normal file
12
src/data/models/exercises/exerciseModel.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { ExerciseGroupModel } from "./exerciseGroupModel"
|
||||
|
||||
export class ExerciseModel {
|
||||
id = -1
|
||||
nameDe: string = ""
|
||||
nameEn: string = ""
|
||||
exerciseNr: number = 0
|
||||
descriptionDe: string = ""
|
||||
descriptionEn: string = ""
|
||||
solved: boolean = false
|
||||
exerciseGroup: ExerciseGroupModel
|
||||
}
|
||||
12
src/data/models/locations/cityApiModel.ts
Normal file
12
src/data/models/locations/cityApiModel.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { LocationApiModel } from "./locationApiModel"
|
||||
|
||||
/**
|
||||
* Replica of the API endpoint /cities
|
||||
*/
|
||||
export class CityApiModel {
|
||||
id: number = -1
|
||||
name: string = ""
|
||||
country: string = ""
|
||||
image: string = ""
|
||||
locations: Array<LocationApiModel>
|
||||
}
|
||||
6
src/data/models/locations/cityModel.ts
Normal file
6
src/data/models/locations/cityModel.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export class CityModel {
|
||||
id: number = -1
|
||||
name: string = ""
|
||||
country: string = ""
|
||||
image: string = ""
|
||||
}
|
||||
10
src/data/models/locations/locationApiModel.ts
Normal file
10
src/data/models/locations/locationApiModel.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { CityModel } from "./cityModel"
|
||||
import { LocationModel } from "./locationModel"
|
||||
|
||||
/**
|
||||
* Replica of the API endpoint /locations
|
||||
*/
|
||||
export class LocationApiModel extends LocationModel {
|
||||
city: CityModel = new CityModel()
|
||||
nrOfConcerts: number = 0
|
||||
}
|
||||
13
src/data/models/locations/locationDetailsApiModel.ts
Normal file
13
src/data/models/locations/locationDetailsApiModel.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { ConcertApiModel } from "../acts/concertApiModel"
|
||||
import { CityModel } from "./cityModel"
|
||||
import { LocationModel } from "./locationModel"
|
||||
import { SeatGroupModel } from "./seatGroupModel"
|
||||
|
||||
/**
|
||||
* Replica of the API endpoint /locations/location/:name
|
||||
*/
|
||||
export class LocationDetailsApiModel extends LocationModel {
|
||||
city: CityModel = new CityModel()
|
||||
concerts: Array<ConcertApiModel> = []
|
||||
seatGroups: Array<SeatGroupModel> = []
|
||||
}
|
||||
10
src/data/models/locations/locationModel.ts
Normal file
10
src/data/models/locations/locationModel.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export class LocationModel {
|
||||
id: number = -1
|
||||
name: string = ""
|
||||
address: string = ""
|
||||
imageIndoor: string = ""
|
||||
imageOutdoor: string = ""
|
||||
capacity: number = 0
|
||||
layout: number = 1
|
||||
urlName: string = ""
|
||||
}
|
||||
10
src/data/models/locations/seatGroupModel.ts
Normal file
10
src/data/models/locations/seatGroupModel.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { SeatRowModel } from "./seatRowModel"
|
||||
|
||||
export class SeatGroupModel {
|
||||
name: string = ""
|
||||
surcharge: number = 0
|
||||
standingArea: boolean = false
|
||||
capacity: number = 0
|
||||
occupied: number = 0
|
||||
seatRows: Array<SeatRowModel>
|
||||
}
|
||||
5
src/data/models/locations/seatModel.ts
Normal file
5
src/data/models/locations/seatModel.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export class SeatModel {
|
||||
id: number = -1
|
||||
seatNr: number = 0
|
||||
state: number = 0
|
||||
}
|
||||
6
src/data/models/locations/seatRowModel.ts
Normal file
6
src/data/models/locations/seatRowModel.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { SeatModel } from "./seatModel"
|
||||
|
||||
export class SeatRowModel {
|
||||
row: number = 0
|
||||
seats: Array<SeatModel>
|
||||
}
|
||||
17
src/data/models/ordering/basketItemModel.ts
Normal file
17
src/data/models/ordering/basketItemModel.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { BandModel } from "../acts/bandModel"
|
||||
import { ConcertModel } from "../acts/concertModel"
|
||||
import { SeatModel } from "../locations/seatModel"
|
||||
|
||||
export class BasketItemModel {
|
||||
concert: ConcertModel
|
||||
band: BandModel = new BandModel()
|
||||
seats: Array<SeatModel> = []
|
||||
price: number
|
||||
|
||||
constructor(concert: ConcertModel, band: BandModel, seat: SeatModel, price: number) {
|
||||
this.concert = concert
|
||||
this.band = band
|
||||
this.seats = [ seat ]
|
||||
this.price = price
|
||||
}
|
||||
}
|
||||
5
src/data/models/ordering/orderModel.ts
Normal file
5
src/data/models/ordering/orderModel.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export class OrderModel {
|
||||
id: number
|
||||
orderedAt: string
|
||||
shipped: boolean
|
||||
}
|
||||
17
src/data/models/ordering/selectedSeatModel.ts
Normal file
17
src/data/models/ordering/selectedSeatModel.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { ConcertModel } from "../acts/concertModel";
|
||||
import { SeatModel } from "../locations/seatModel";
|
||||
|
||||
export class SelectedSeatModel {
|
||||
seat: SeatModel
|
||||
seatRow: number
|
||||
seatGroupName: string
|
||||
concert: ConcertModel
|
||||
price: number
|
||||
|
||||
constructor(seat: SeatModel, seatRow: number, seatGroupName: string, concert: ConcertModel) {
|
||||
this.seat = seat
|
||||
this.seatRow = seatRow
|
||||
this.seatGroupName = seatGroupName
|
||||
this.concert = concert
|
||||
}
|
||||
}
|
||||
8
src/data/models/ordering/ticketApiModel.ts
Normal file
8
src/data/models/ordering/ticketApiModel.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { ConcertApiModel } from "../acts/concertApiModel";
|
||||
import { SeatModel } from "../locations/seatModel";
|
||||
import { TicketModel } from "./ticketModel";
|
||||
|
||||
export class TicketApiModel extends TicketModel {
|
||||
concert: ConcertApiModel
|
||||
seat: SeatModel
|
||||
}
|
||||
6
src/data/models/ordering/ticketModel.ts
Normal file
6
src/data/models/ordering/ticketModel.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export class TicketModel {
|
||||
id: number
|
||||
orderId: number = -1
|
||||
orderPrice: number = 0
|
||||
concertId: number = 0
|
||||
}
|
||||
10
src/data/models/user/accountApiModel.ts
Normal file
10
src/data/models/user/accountApiModel.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { AccountModel } from "./accountModel"
|
||||
import { AccountRole } from "./accountRole"
|
||||
import { AddressModel } from "./addressModel"
|
||||
import { PaymentModel } from "./paymentModel"
|
||||
|
||||
export class AccountApiModel extends AccountModel {
|
||||
addresses: Array<AddressModel>
|
||||
payments: Array<PaymentModel>
|
||||
accountRole: AccountRole = new AccountRole()
|
||||
}
|
||||
8
src/data/models/user/accountModel.ts
Normal file
8
src/data/models/user/accountModel.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export class AccountModel {
|
||||
id: number
|
||||
username: string = ""
|
||||
password: string = ""
|
||||
email: string = ""
|
||||
firstName: string = ""
|
||||
lastName: string = ""
|
||||
}
|
||||
5
src/data/models/user/accountRole.ts
Normal file
5
src/data/models/user/accountRole.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export class AccountRole {
|
||||
name: string = ""
|
||||
privilegeBuy: boolean = false
|
||||
privilegeAdminPanel: boolean = false
|
||||
}
|
||||
6
src/data/models/user/addressModel.ts
Normal file
6
src/data/models/user/addressModel.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export class AddressModel {
|
||||
street: string = ""
|
||||
houseNumber: number
|
||||
postalCode: number
|
||||
city: string = ""
|
||||
}
|
||||
4
src/data/models/user/paymentModel.ts
Normal file
4
src/data/models/user/paymentModel.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export class PaymentModel {
|
||||
bankName: string = ""
|
||||
iban: string = ""
|
||||
}
|
||||
21
src/layouts/accountSubPageLayout.vue
Normal file
21
src/layouts/accountSubPageLayout.vue
Normal file
@@ -0,0 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
import { useRouter } from 'vue-router';
|
||||
import outlinedButton from '@/components/basics/outlinedButton.vue';
|
||||
|
||||
const router = useRouter()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-container max-width="1000">
|
||||
<!-- Back to previous page -->
|
||||
<v-row>
|
||||
<v-col>
|
||||
<outlined-button prepend-icon="mdi-arrow-left" @click="router.back()" >
|
||||
{{ $t('misc.onePageBack') }}
|
||||
</outlined-button>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<slot></slot>
|
||||
</v-container>
|
||||
</template>
|
||||
52
src/layouts/adminDataLayout.vue
Normal file
52
src/layouts/adminDataLayout.vue
Normal file
@@ -0,0 +1,52 @@
|
||||
<script setup lang="ts">
|
||||
import outlinedButton from '@/components/basics/outlinedButton.vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
const fetchInProgress = defineModel("fetchInProgress", { default: false })
|
||||
const router = useRouter()
|
||||
|
||||
defineProps({
|
||||
addButtonString: String,
|
||||
hideAddButton: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
onAddClick: {
|
||||
type: Function,
|
||||
default: () => {}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-container>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<outlined-button
|
||||
prepend-icon="mdi-arrow-left"
|
||||
@click="router.go(-1)"
|
||||
>
|
||||
{{ $t('misc.onePageBack') }}
|
||||
</outlined-button>
|
||||
</v-col>
|
||||
|
||||
<v-col class="text-end">
|
||||
<outlined-button
|
||||
v-if="!hideAddButton"
|
||||
prepend-icon="mdi-plus"
|
||||
color="green"
|
||||
:disabled="fetchInProgress"
|
||||
@click="onAddClick()"
|
||||
>
|
||||
{{ addButtonString }}
|
||||
</outlined-button>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row>
|
||||
<v-col>
|
||||
<slot></slot>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</template>
|
||||
252
src/locales/de.json
Normal file
252
src/locales/de.json
Normal file
@@ -0,0 +1,252 @@
|
||||
{
|
||||
"location": {
|
||||
"allLocations": "Alle Veranstaltungsorte",
|
||||
"topLocations": "Top Veranstaltungsorte",
|
||||
"location": "Veranstaltungsort | Veranstaltungsorte",
|
||||
"noLocationsFound": "Keine Veranstaltungsorte gefunden",
|
||||
"city": "Stadt | Städte",
|
||||
"stage": "Bühne | Bühnen",
|
||||
"seat": {
|
||||
"seatRow": "Sitzreihe | Sitzreihen",
|
||||
"seat": "Sitz | Sitze",
|
||||
"seatGroup": "Sitzgruppe | Sitzgruppen",
|
||||
"standingPlace": "Stehplatz | Stehplätze",
|
||||
"seatPlan": "Saalplan | Saalpläne",
|
||||
"seatSelection": "Sitzauswahl",
|
||||
"standingArea": "Stehbereich"
|
||||
},
|
||||
"nrOfConcerts": "Konzerte",
|
||||
"capacity": "Kapazität",
|
||||
"layoutNr": "Layout Nr",
|
||||
"name": "Location Name",
|
||||
"address": "Adresse",
|
||||
"imageIndoor": "Bild Innenraum",
|
||||
"imageOutdoor": "Bild Außen",
|
||||
"addLocation": "Neue Location hinzufügen"
|
||||
},
|
||||
"concert": {
|
||||
"concert": "Konzert | Konzerte",
|
||||
"allConcerts": "Alle Konzerte",
|
||||
"upcomingConcerts": "Nächste Konzerte",
|
||||
"goToTheConcert": "Zum Konzert",
|
||||
"noConcertsFound": "Keine Konzerte gefunden",
|
||||
"selectedConcert": "Ausgewähltes Konzert",
|
||||
"concertSoldOut": "Ausverkauft",
|
||||
"addNewConcert": "Neues Konzert hinzufügen",
|
||||
"date": "Datum",
|
||||
"name": "Name des Konzertes",
|
||||
"offered": "Angeboten",
|
||||
"image": "Veranstaltungsbild",
|
||||
"price": "Preis",
|
||||
"inStock": "Übrige Tickets"
|
||||
},
|
||||
"band": {
|
||||
"band": "Band | Bands",
|
||||
"allBands": "Alle Bands",
|
||||
"noBandFound": "Keine Band gefunden",
|
||||
"rating": "Bewertung | Bewertungen",
|
||||
"bandMember": "Band Mitglieder",
|
||||
"image": "Foto | Fotos",
|
||||
"genre": "Genre | Genres",
|
||||
"name": "Bandname",
|
||||
"editBand": "Band bearbeiten",
|
||||
"foundingYear": "Gründungsjahr",
|
||||
"descriptionDe": "Beschreibung Deutsch",
|
||||
"descriptionEn": "Beschreibung Englisch",
|
||||
"addNewBand": "Neue Band hinzufügen",
|
||||
"logo": "Band Logo",
|
||||
"imageMember": "Bilder Mitglieder",
|
||||
"addNewGenre": "Neues Genre hinzufügen",
|
||||
"editGenre": "Genre bearbeiten",
|
||||
"bandDetails": "Band Details",
|
||||
"withoutConcert": "ohne Konzert"
|
||||
},
|
||||
"ticket": {
|
||||
"ticket": "Ticket | Tickets"
|
||||
},
|
||||
"account": {
|
||||
"account": "Account | Accounts",
|
||||
"register": "Account erstellen",
|
||||
"masterData": "Stammdaten",
|
||||
"noAddresses": "Keine Adressen gefunden",
|
||||
"noPayments": "Keine Bezahlarten gefunden",
|
||||
"wrongIban": "Falsches IBAN Format, nur deutsche IBAN-Nummern erlaubt!",
|
||||
"emailIsNotValid": "Ungültige E-Mail Addresse",
|
||||
"emailRequired": "E-Mail-Adresse benötigt",
|
||||
"accountManagement": "Account verwalten",
|
||||
"accountManagementDescription": "Persönliche Daten, Adressen, Bezahlmethoden",
|
||||
"login": {
|
||||
"pleaseLoginToOrder": "Bitte anmelden zum bestellen",
|
||||
"backToLogin": "Zurück zum Login",
|
||||
"login": "Login"
|
||||
},
|
||||
"logout": {
|
||||
"logout": "Ausloggen",
|
||||
"logoutDescription": "Aktuellen Useraccount ausloggen"
|
||||
},
|
||||
"userData": {
|
||||
"username": "Username",
|
||||
"password": "Passwort",
|
||||
"address": "Adresse | Adressen",
|
||||
"payment": "Bezahlart | Bezahlarten",
|
||||
"email": "E-Mail-Adresse",
|
||||
"firstName": "Vorname",
|
||||
"lastName": "Nachname",
|
||||
"street": "Straße",
|
||||
"houseNumber": "Hausnummer",
|
||||
"postalCode": "Postleitzahl",
|
||||
"placeOfResidence": "Wohnort",
|
||||
"bankName": "Name der Bank",
|
||||
"iban": "IBAN"
|
||||
},
|
||||
"deleteAccount": {
|
||||
"deleteAccount": "Account löschen",
|
||||
"dialog": {
|
||||
"title": "Account löschen?",
|
||||
"description": "Soll der Account wirklich gelöscht werden? Dieser kann nicht mehr wiederhergestellt werden!"
|
||||
}
|
||||
},
|
||||
"addNewAccount": "Neuen Account hinzufügen",
|
||||
"accountRole": "Account Rolle",
|
||||
"noRealPaymentsNeeded": "Keine echten Kontodaten nötig!",
|
||||
"administrator": "Administrator | Administratoren"
|
||||
},
|
||||
"order": {
|
||||
"oclock": "Uhr",
|
||||
"ordering": "Bestellabschluss",
|
||||
"orderSummary": "Bestellübersicht",
|
||||
"takeOrder": "Bestellung ausführen",
|
||||
"noOrders": "Keine Bestellungen gefunden",
|
||||
"orderedAt": "Bestellt am",
|
||||
"noOrdersText": "Bisher wurden keine Bestellungen von diesem Account getätigt. Gehe zum Warenkorb und bestelle!",
|
||||
"ordersDescription": "Übersicht aller getätigten Bestellungen",
|
||||
"order": "Bestellung | Bestellungen",
|
||||
"notShipped": "noch nicht versendet"
|
||||
},
|
||||
"basket": {
|
||||
"addToBasket": "Zum Warenkorb hinzufügen",
|
||||
"emptyBasketTitle": "Keine Tickets im Warenkorb",
|
||||
"emptyBasketText": "Gehe zu unseren Konzerten und lege Tickets in den Warenkorb",
|
||||
"basket": "Warenkorb"
|
||||
},
|
||||
"preferences": {
|
||||
"pageSetup": "Seiteneinstellungen",
|
||||
"selectedTheme": "Ausgewähltes Theme",
|
||||
"language": "Sprache | Sprachen",
|
||||
"serverState": "Server Status",
|
||||
"systemSetup": "Systemeinstellungen",
|
||||
"resetDatabase": {
|
||||
"resetDatabase": "Datenbank zurücksetzen",
|
||||
"dialog": {
|
||||
"title": "Datenbank zurücksetzen?",
|
||||
"description": "Soll die Datenbank des Servers wirklich zurückgesetzt werden? Dies kann nicht rückgänig gemacht werden! Der Bearbeitungsfortschritt der Aufgaben wird nicht gelöscht."
|
||||
}
|
||||
},
|
||||
"resetExerciseProgress": {
|
||||
"resetExerciseProgress": "Aufgabenfortschritt zurücksetzen",
|
||||
"dialog": {
|
||||
"title": "Übungsfortschritt zurücksetzen?",
|
||||
"description": "Soll der Bearbeitungsfortschritt der Übungen wirklich zurückgesetzt werden? Dies kann nicht rückgänig gemacht werden!"
|
||||
}
|
||||
},
|
||||
"aboutProject": "Über das Projekt",
|
||||
"resetFirstStartup": "Einrichtungsassistenten starten",
|
||||
"factoryReset": {
|
||||
"factoryReset": "Zurücksetzen auf Werkseinstellungen",
|
||||
"dialog": {
|
||||
"title": "Aus Werkseinstellungen zurücksetzen?",
|
||||
"description": "Sollen alle Einstellungen und Daten auf Werkseinstellungen zurückgesetzt werden? Alle Änderungen und Fortschritte gehen verloren!"
|
||||
}
|
||||
}
|
||||
},
|
||||
"help": {
|
||||
"scoreBoard": {
|
||||
"exerciseGroupNr": "Aufgabengruppe {0}: ",
|
||||
"exerciseNr": "Aufgabe {0}.{1}: "
|
||||
}
|
||||
},
|
||||
"bannerMessages": {
|
||||
"loginSuccessful": "Login erfolgreich!",
|
||||
"wrongLogin": "Falscher Username oder falsches Passwort!",
|
||||
"error": "Es ist ein Fehler aufgetreten...",
|
||||
"databaseResetSuccessful": "Datenbank erfolgreich zurück gesetzt!",
|
||||
"exerciseProgressResetSuccessful": "Aufgabenfortschritt erfolgreich zurück gesetzt!",
|
||||
"registerSuccessful": "Account erfolgreich erstellt!",
|
||||
"registerError": "Fehler beim Erstellen des Accounts",
|
||||
"usernameInUse": "Der Accountname ist bereits vergeben!",
|
||||
"accountUpdated": "Account erfolgreich aktualisiert",
|
||||
"logoutSuccessful": "Logout erfolgreich",
|
||||
"orderPlaceSuccessfull": "Bestellung erfolgreich aufgegeben",
|
||||
"basketTicketAdded": "Ticket zum Warenkorb hinzugefügt",
|
||||
"basketTicketRemoved": "Ticket aus Warenkorb entfernt",
|
||||
"exerciseSolvedNr": "Aufgabe {0}.{1} gelöst!",
|
||||
"bandDeleteError": "Fehler beim Löschen der Band",
|
||||
"bandDeleteSuccessful": "Band erfolgreich gelöscht",
|
||||
"bandSavedError": "Fehler beim Speichern der Band",
|
||||
"bandSavedSuccessful": "Band erfolgreich gespeichert",
|
||||
"genreDeleteError": "Fehler beim Löschen des Genres",
|
||||
"genreDeleteSuccessful": "Genre erfolgreich gelöscht",
|
||||
"genreSavedError": "Fehler beim Speichern des Genres",
|
||||
"genreSavedSuccessful": "Genre erfolgreich gespeichert"
|
||||
},
|
||||
"misc": {
|
||||
"404": {
|
||||
"title": "Seite nicht gefunden",
|
||||
"headline": "404"
|
||||
},
|
||||
"sortBy": "Sortieren nach",
|
||||
"greeting": "Hallo {msg}",
|
||||
"from": "ab",
|
||||
"youAreHere": "Du bist hier:",
|
||||
"date": "Datum | Daten",
|
||||
"totalPrice": "Gesamtpreis",
|
||||
"price": "Preis | Preise",
|
||||
"quantity": "Anzahl",
|
||||
"loading": "Lade...",
|
||||
"onePageBack": "Zurück zur vorherigen Seite",
|
||||
"enterSomeKeywords": "Füge Schlagworte ein um nach Bands, Konzerten und Veranstaltungsorten zu suchen",
|
||||
"actions": {
|
||||
"add": "Hinzufügen",
|
||||
"remove": "Entfernen",
|
||||
"save": "Speichern",
|
||||
"filtering": "Filtern",
|
||||
"cancel": "Abbrechen",
|
||||
"more": "Mehr",
|
||||
"confirm": "Bestätigen",
|
||||
"next": "Weiter"
|
||||
},
|
||||
"validation": {
|
||||
"required": "Darf nicht leer bleiben",
|
||||
"noDigitsAllowed": "Zahlen sind nicht erlaubt",
|
||||
"notEnoughChars": "Nicht wenige Zeichen",
|
||||
"tooMuchChars": "Zu viele Zeichen",
|
||||
"onlyDigitsAllowed": "Nur Zahlen erlaubt",
|
||||
"digitsAtStartNeeded": "Muss mit einer Zahl beginnen"
|
||||
},
|
||||
"file": "Datei | Dateien",
|
||||
"folder": "Ordner | Ordner",
|
||||
"uploadFile": "Datei hochladen",
|
||||
"firstStartup": {
|
||||
"title": "Ersteinrichtung",
|
||||
"description": "Die Datenbank wird eingerichtet. Bitte warten...",
|
||||
"createDatabase": "Erstelle Datenbank...",
|
||||
"complete": "Fertig",
|
||||
"finished": "Abgeschlossen",
|
||||
"createExercises": "Erstelle Aufgaben...",
|
||||
"connectToServer": "Server",
|
||||
"database": "Datenbank",
|
||||
"exercises": "Aufgaben",
|
||||
"userData": "Persönliche Daten"
|
||||
},
|
||||
"user": "Angaben zur Person",
|
||||
"registrationNumber": "Matrikelnummer",
|
||||
"yourFullName": "Vollständiger Name",
|
||||
"chooseFile": "Datei auswählen",
|
||||
"chooseDestinationFolder": "Zielordner auswählen",
|
||||
"upload": "Hochladen",
|
||||
"fulfillYourPersonalDataFirst": "Gehe zu den Einstellungen und fülle deinen Namen und deine Matrikelnummer aus"
|
||||
},
|
||||
"genre": {
|
||||
"withoutBand": "ohne Band"
|
||||
}
|
||||
}
|
||||
252
src/locales/en.json
Normal file
252
src/locales/en.json
Normal file
@@ -0,0 +1,252 @@
|
||||
{
|
||||
"location": {
|
||||
"allLocations": "All Locations",
|
||||
"topLocations": "Top Locations",
|
||||
"location": "Location | Locations",
|
||||
"noLocationsFound": "No Locations found",
|
||||
"city": "City | Cities",
|
||||
"stage": "Stage | Stages",
|
||||
"seat": {
|
||||
"seatRow": "Seat Row | Seat Rows",
|
||||
"seat": "Seat | Seats",
|
||||
"seatGroup": "Seat Group | Seat Groups",
|
||||
"standingPlace": "Standing place | Standing places",
|
||||
"seatPlan": "Seat plan | Seat plans",
|
||||
"seatSelection": "Seat selection",
|
||||
"standingArea": "Seat area"
|
||||
},
|
||||
"nrOfConcerts": "Concerts",
|
||||
"capacity": "Capacity",
|
||||
"layoutNr": "Layout Nr",
|
||||
"name": "Location Name",
|
||||
"address": "Adress",
|
||||
"imageIndoor": "Image Indoor",
|
||||
"imageOutdoor": "Image Outdoor",
|
||||
"addLocation": "Add new Location"
|
||||
},
|
||||
"concert": {
|
||||
"concert": "Concert | Concerts",
|
||||
"allConcerts": "All Concerts",
|
||||
"upcomingConcerts": "Upcoming Concerts",
|
||||
"goToTheConcert": "Go to concert",
|
||||
"noConcertsFound": "No Concerts found",
|
||||
"selectedConcert": "Selected Concert",
|
||||
"concertSoldOut": "Sold out",
|
||||
"addNewConcert": "Add new concert",
|
||||
"date": "Date",
|
||||
"name": "Name of concert",
|
||||
"offered": "Offered",
|
||||
"image": "Image of concert",
|
||||
"price": "Price",
|
||||
"inStock": "Open tickets"
|
||||
},
|
||||
"band": {
|
||||
"band": "Band | Bands",
|
||||
"allBands": "All Bands",
|
||||
"noBandFound": "No Band found",
|
||||
"rating": "Rating | Ratings",
|
||||
"bandMember": "Band Member",
|
||||
"image": "Photo | Photos",
|
||||
"genre": "Genre | Genres",
|
||||
"name": "Name of band",
|
||||
"editBand": "Edit band",
|
||||
"foundingYear": "Founding Year",
|
||||
"descriptionDe": "Description German",
|
||||
"descriptionEn": "Description English",
|
||||
"addNewBand": "Add new Band",
|
||||
"logo": "Band logo",
|
||||
"imageMember": "Images Members",
|
||||
"addNewGenre": "Add new Genre",
|
||||
"editGenre": "Edit Genre",
|
||||
"bandDetails": "Band Details",
|
||||
"withoutConcert": "without concert"
|
||||
},
|
||||
"ticket": {
|
||||
"ticket": "Ticket | Tickets"
|
||||
},
|
||||
"account": {
|
||||
"account": "Account | Accounts",
|
||||
"register": "Register",
|
||||
"masterData": "Master data",
|
||||
"noAddresses": "No addresses found",
|
||||
"noPayments": "No payments found",
|
||||
"wrongIban": "Wrong IBAN format, only German IBAN-numbers allowed!",
|
||||
"emailIsNotValid": "E-Mail not valid",
|
||||
"emailRequired": "E-Mail required",
|
||||
"accountManagement": "Manage Account",
|
||||
"accountManagementDescription": "Personal data, addresses, payments",
|
||||
"login": {
|
||||
"pleaseLoginToOrder": "Please login to order",
|
||||
"backToLogin": "Back to Login",
|
||||
"login": "Login"
|
||||
},
|
||||
"logout": {
|
||||
"logout": "Logout",
|
||||
"logoutDescription": "Logout current user account"
|
||||
},
|
||||
"userData": {
|
||||
"username": "Username",
|
||||
"password": "Password",
|
||||
"address": "Adress | Addresses",
|
||||
"payment": "Payment | Payments",
|
||||
"email": "E-Mail address",
|
||||
"firstName": "First name",
|
||||
"lastName": "Last name",
|
||||
"street": "Street",
|
||||
"houseNumber": "House number",
|
||||
"postalCode": "Postal code",
|
||||
"placeOfResidence": "Place of residence",
|
||||
"bankName": "Name of bank",
|
||||
"iban": "IBAN"
|
||||
},
|
||||
"deleteAccount": {
|
||||
"deleteAccount": "Delete Account",
|
||||
"dialog": {
|
||||
"title": "Delete Account?",
|
||||
"description": "Do you really want to delete this account? This is permanent!"
|
||||
}
|
||||
},
|
||||
"addNewAccount": "Add new account",
|
||||
"accountRole": "Account Role",
|
||||
"noRealPaymentsNeeded": "No real payment data required!",
|
||||
"administrator": "Administrator"
|
||||
},
|
||||
"order": {
|
||||
"oclock": "o'clock",
|
||||
"ordering": "Finish order process",
|
||||
"orderSummary": "Order overview",
|
||||
"takeOrder": "Execute order",
|
||||
"noOrders": "No orders found",
|
||||
"orderedAt": "Ordered at",
|
||||
"noOrdersText": "Bisher wurden keine Bestellungen von diesem Account getätigt. Gehe zum Warenkorb und bestelle!",
|
||||
"ordersDescription": "Overview of all placed orders",
|
||||
"order": "Order | Orders",
|
||||
"notShipped": "don't shipped"
|
||||
},
|
||||
"basket": {
|
||||
"addToBasket": "Add to basket",
|
||||
"emptyBasketTitle": "No tickets in basket",
|
||||
"emptyBasketText": "Go to our concerts and place some ticket in the basket",
|
||||
"basket": "Basket"
|
||||
},
|
||||
"preferences": {
|
||||
"pageSetup": "Page setup",
|
||||
"selectedTheme": "Selected Theme",
|
||||
"language": "Language | Languages",
|
||||
"serverState": "Server Status",
|
||||
"systemSetup": "System preferences",
|
||||
"resetDatabase": {
|
||||
"resetDatabase": "Reset database",
|
||||
"dialog": {
|
||||
"title": "Reset database?",
|
||||
"description": "Do you really want to reset the database? This is permanent! Exercise progress will not be affected"
|
||||
}
|
||||
},
|
||||
"resetExerciseProgress": {
|
||||
"resetExerciseProgress": "Reset exercise progress",
|
||||
"dialog": {
|
||||
"title": "Reset exercise progress?",
|
||||
"description": "Do you really want to reset the exercise progress? This is permanent!"
|
||||
}
|
||||
},
|
||||
"aboutProject": "About the project",
|
||||
"resetFirstStartup": "Start first startup dialog",
|
||||
"factoryReset": {
|
||||
"factoryReset": "Factory reset",
|
||||
"dialog": {
|
||||
"title": "Factory reset?",
|
||||
"description": "Do you really want to reset everything? Every change will be lost!"
|
||||
}
|
||||
}
|
||||
},
|
||||
"help": {
|
||||
"scoreBoard": {
|
||||
"exerciseGroupNr": "Exercise Group {0}: ",
|
||||
"exerciseNr": "Exercise {0}.{1}: "
|
||||
}
|
||||
},
|
||||
"bannerMessages": {
|
||||
"loginSuccessful": "Login successfull!",
|
||||
"wrongLogin": "Wrong username or wrong password!",
|
||||
"error": "There is some error...",
|
||||
"databaseResetSuccessful": "Database successfully resetted!",
|
||||
"exerciseProgressResetSuccessful": "Exercise progress successfully resetted!",
|
||||
"registerSuccessful": "Account successfully created!",
|
||||
"registerError": "Error on register account",
|
||||
"usernameInUse": "The username is already in use!",
|
||||
"accountUpdated": "Account successfully updated",
|
||||
"logoutSuccessful": "Logout successfull",
|
||||
"orderPlaceSuccessfull": "Order successfully placed",
|
||||
"basketTicketAdded": "Added ticket to basket",
|
||||
"basketTicketRemoved": "Removed ticket from basket",
|
||||
"exerciseSolvedNr": "Exercise {0}.{1} solved!",
|
||||
"bandDeleteError": "Error on deleting band",
|
||||
"bandDeleteSuccessful": "Band successfully deleted",
|
||||
"bandSavedError": "Error on saving band",
|
||||
"bandSavedSuccessful": "Band successfully saved",
|
||||
"genreDeleteError": "Error on deleting Genre",
|
||||
"genreDeleteSuccessful": "Genre successfully deleted",
|
||||
"genreSavedError": "Error on saving genre",
|
||||
"genreSavedSuccessful": "Genre successfully saved"
|
||||
},
|
||||
"misc": {
|
||||
"404": {
|
||||
"title": "Page not found",
|
||||
"headline": "404"
|
||||
},
|
||||
"sortBy": "Sort by",
|
||||
"greeting": "Hello {msg}",
|
||||
"from": "from",
|
||||
"youAreHere": "You are here:",
|
||||
"date": "Date | Dates",
|
||||
"totalPrice": "Total price",
|
||||
"price": "Price | Prices",
|
||||
"quantity": "Quantity",
|
||||
"loading": "Loading...",
|
||||
"onePageBack": "Back to previous page",
|
||||
"enterSomeKeywords": "Enter keywords to search for bands, concerts and locations",
|
||||
"actions": {
|
||||
"add": "Add",
|
||||
"remove": "Remove",
|
||||
"save": "Save",
|
||||
"filtering": "Filter",
|
||||
"cancel": "Cancel",
|
||||
"more": "More",
|
||||
"confirm": "Confirm",
|
||||
"next": "Next"
|
||||
},
|
||||
"validation": {
|
||||
"required": "Must not remain empty",
|
||||
"noDigitsAllowed": "Numbers are not allowed",
|
||||
"notEnoughChars": "Not enough characters",
|
||||
"tooMuchChars": "Too much characters",
|
||||
"onlyDigitsAllowed": "Only numbers are allowed",
|
||||
"digitsAtStartNeeded": "Must start with a number"
|
||||
},
|
||||
"file": "File | Files",
|
||||
"folder": "folder | folders",
|
||||
"uploadFile": "Upload file",
|
||||
"firstStartup": {
|
||||
"title": "First startup",
|
||||
"description": "Creating database. Please wait...",
|
||||
"createDatabase": "Create Database...",
|
||||
"complete": "Complete",
|
||||
"createExercises": "Create Exercises...",
|
||||
"finished": "Finished",
|
||||
"connectToServer": "Server",
|
||||
"database": "Database",
|
||||
"exercises": "Exercises",
|
||||
"userData": "User data"
|
||||
},
|
||||
"user": "About person",
|
||||
"registrationNumber": "Matrikel number",
|
||||
"yourFullName": "Full name",
|
||||
"chooseFile": "Choose file",
|
||||
"chooseDestinationFolder": "Choose destination folder",
|
||||
"upload": "Upload",
|
||||
"fulfillYourPersonalDataFirst": "Go to settings and enter your name and the right register number"
|
||||
},
|
||||
"genre": {
|
||||
"withoutBand": "without Band"
|
||||
}
|
||||
}
|
||||
18
src/main.ts
Normal file
18
src/main.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
|
||||
import vuetify from './plugins/vuetify'
|
||||
import router from './plugins/router'
|
||||
import pinia from './plugins/pinia'
|
||||
import { i18n } from './plugins/i18n'
|
||||
import { useFeedbackStore } from './stores/feedback.store'
|
||||
|
||||
createApp(App)
|
||||
.use(vuetify)
|
||||
.use(router)
|
||||
.use(pinia)
|
||||
.use(i18n)
|
||||
.mount('#app')
|
||||
|
||||
const feedbackStore = useFeedbackStore()
|
||||
feedbackStore.$i18n = i18n
|
||||
100
src/pages/account/accountDataPage/accountDataCard.vue
Normal file
100
src/pages/account/accountDataPage/accountDataCard.vue
Normal file
@@ -0,0 +1,100 @@
|
||||
<script setup lang="ts">
|
||||
import cardView from '@/components/basics/cardView.vue';
|
||||
import { useAccountStore } from '@/stores/account.store';
|
||||
import { useFeedbackStore } from '@/stores/feedback.store';
|
||||
|
||||
const accountStore = useAccountStore()
|
||||
const feedbackStore = useFeedbackStore()
|
||||
|
||||
const passwordRules = [
|
||||
value => {
|
||||
if (value) {
|
||||
return true
|
||||
} else {
|
||||
return feedbackStore.i18n.t('misc.validation.required')
|
||||
}
|
||||
},
|
||||
value => {
|
||||
if (value?.length >= 8) {
|
||||
return true
|
||||
} else {
|
||||
return feedbackStore.i18n.t('misc.validation.notEnoughChars')
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
const stringRules = [
|
||||
value => {
|
||||
if (value) {
|
||||
return true
|
||||
} else {
|
||||
return feedbackStore.i18n.t('misc.validation.required')
|
||||
}
|
||||
},
|
||||
value => {
|
||||
if (/[^0-9]/.test(value)) {
|
||||
return true
|
||||
} else {
|
||||
return feedbackStore.i18n.t('misc.validation.noDigitsAllowed')
|
||||
}
|
||||
},
|
||||
value => {
|
||||
if (value?.length >= 3) {
|
||||
return true
|
||||
} else {
|
||||
return feedbackStore.i18n.t('misc.validation.notEnoughChars')
|
||||
}
|
||||
}
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<card-view
|
||||
:title="$t('account.masterData')"
|
||||
icon="mdi-account"
|
||||
>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-text-field
|
||||
:label="$t('account.userData.email')"
|
||||
v-model="accountStore.userAccount.email"
|
||||
disabled
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-text-field
|
||||
:label="$t('account.userData.username')"
|
||||
v-model="accountStore.userAccount.username"
|
||||
disabled
|
||||
/>
|
||||
</v-col>
|
||||
<v-col>
|
||||
<v-text-field
|
||||
:label="$t('account.userData.password')"
|
||||
v-model="accountStore.userAccount.password"
|
||||
type="password"
|
||||
:rules="passwordRules"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-text-field
|
||||
:label="$t('account.userData.firstName')"
|
||||
v-model="accountStore.userAccount.firstName"
|
||||
:rules="stringRules"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col>
|
||||
<v-text-field
|
||||
:label="$t('account.userData.lastName')"
|
||||
v-model="accountStore.userAccount.lastName"
|
||||
:rules="stringRules"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</card-view>
|
||||
</template>
|
||||
48
src/pages/account/accountDataPage/accountManagingCard.vue
Normal file
48
src/pages/account/accountDataPage/accountManagingCard.vue
Normal file
@@ -0,0 +1,48 @@
|
||||
<script setup lang="ts">
|
||||
import cardView from '@/components/basics/cardView.vue';
|
||||
import confirmDialog from '@/components/basics/confirmDialog.vue';
|
||||
import outlinedButton from '@/components/basics/outlinedButton.vue';
|
||||
import { useAccountStore } from '@/stores/account.store';
|
||||
import { ref } from 'vue';
|
||||
|
||||
const showConfirmDialog = ref(false)
|
||||
const accountStore = useAccountStore()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<card-view
|
||||
:title="$t('account.accountManagement')"
|
||||
icon="mdi-account-edit"
|
||||
>
|
||||
<v-row>
|
||||
<v-col class="d-flex justify-center align-center">
|
||||
<outlined-button
|
||||
prepend-icon="mdi-delete"
|
||||
color="red"
|
||||
:loading="accountStore.fetchInProgress"
|
||||
@click="showConfirmDialog = true"
|
||||
>
|
||||
{{ $t("account.deleteAccount.deleteAccount") }}
|
||||
</outlined-button>
|
||||
</v-col>
|
||||
|
||||
<v-col class="d-flex justify-center align-center">
|
||||
<outlined-button
|
||||
prepend-icon="mdi-content-save"
|
||||
color="green"
|
||||
:loading="accountStore.fetchInProgress"
|
||||
@click="accountStore.updateAccount()"
|
||||
>
|
||||
{{ $t("misc.actions.save") }}
|
||||
</outlined-button>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</card-view>
|
||||
|
||||
<confirm-dialog
|
||||
v-model="showConfirmDialog"
|
||||
:title="$t('account.deleteAccount.dialog.title')"
|
||||
:description="$t('account.deleteAccount.dialog.description')"
|
||||
:on-confirm="() => accountStore.deleteAccount(accountStore.userAccount)"
|
||||
/>
|
||||
</template>
|
||||
95
src/pages/account/accountDataPage/addressesCard.vue
Normal file
95
src/pages/account/accountDataPage/addressesCard.vue
Normal file
@@ -0,0 +1,95 @@
|
||||
<script setup lang="ts">
|
||||
import cardView from '@/components/basics/cardView.vue';
|
||||
import { useAccountStore } from '@/stores/account.store';
|
||||
import outlinedButton from '@/components/basics/outlinedButton.vue';
|
||||
import { AddressModel } from '@/data/models/user/addressModel';
|
||||
import { getNumberStartRules, getPostalRules, getStringRules } from '@/scripts/validationRules';
|
||||
|
||||
const accountStore = useAccountStore()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<card-view
|
||||
icon="mdi-home"
|
||||
:title="$t('account.userData.address')"
|
||||
>
|
||||
<v-expansion-panels v-if="accountStore.userAccount.addresses.length > 0">
|
||||
<v-expansion-panel
|
||||
v-for="address in accountStore.userAccount.addresses"
|
||||
color="primary"
|
||||
>
|
||||
<template #title>
|
||||
{{ address.street + ' ' + address.houseNumber }}
|
||||
</template>
|
||||
|
||||
<template #text>
|
||||
<v-row class="pt-5">
|
||||
<v-col>
|
||||
<v-text-field
|
||||
:label="$t('account.userData.street')"
|
||||
v-model="address.street"
|
||||
:rules="getStringRules()"
|
||||
clearable
|
||||
/>
|
||||
</v-col>
|
||||
<v-col>
|
||||
<v-text-field
|
||||
:label="$t('account.userData.houseNumber')"
|
||||
v-model="address.houseNumber"
|
||||
:rules="getNumberStartRules()"
|
||||
clearable
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-text-field
|
||||
:label="$t('account.userData.postalCode')"
|
||||
v-model="address.postalCode"
|
||||
:rules="getPostalRules()"
|
||||
clearable
|
||||
/>
|
||||
</v-col>
|
||||
<v-col>
|
||||
<v-text-field
|
||||
:label="$t('account.userData.placeOfResidence')"
|
||||
v-model="address.city"
|
||||
:rules="getStringRules()"
|
||||
clearable
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row>
|
||||
<v-col class="d-flex justify-center align-center">
|
||||
<outlined-button
|
||||
@click="accountStore.removeAddress(address)"
|
||||
color="red"
|
||||
prepend-icon="mdi-delete"
|
||||
>
|
||||
{{ $t('misc.actions.remove') }}
|
||||
</outlined-button>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
</v-expansion-panel>
|
||||
</v-expansion-panels>
|
||||
|
||||
<v-empty-state
|
||||
v-else
|
||||
:title="$t('account.noAddresses')"
|
||||
icon="mdi-home-off"
|
||||
/>
|
||||
|
||||
<template #actions>
|
||||
<outlined-button
|
||||
@click="accountStore.userAccount.addresses.push(new AddressModel())"
|
||||
prepend-icon="mdi-plus"
|
||||
color="green"
|
||||
>
|
||||
{{ $t('misc.actions.add') }}
|
||||
</outlined-button>
|
||||
</template>
|
||||
</card-view>
|
||||
</template>
|
||||
38
src/pages/account/accountDataPage/index.vue
Normal file
38
src/pages/account/accountDataPage/index.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<script setup lang="ts">
|
||||
import accountDataCard from './accountDataCard.vue';
|
||||
import accountManagingCard from './accountManagingCard.vue';
|
||||
import addressesCard from './addressesCard.vue';
|
||||
import paymentsCard from './paymentsCard.vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import accountSubPageLayout from '@/layouts/accountSubPageLayout.vue';
|
||||
|
||||
const router = useRouter()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<account-sub-page-layout>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<account-data-card />
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row>
|
||||
<v-col>
|
||||
<addresses-card />
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row>
|
||||
<v-col>
|
||||
<payments-card />
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row>
|
||||
<v-col>
|
||||
<account-managing-card />
|
||||
</v-col>
|
||||
</v-row>
|
||||
</account-sub-page-layout>
|
||||
</template>
|
||||
93
src/pages/account/accountDataPage/paymentsCard.vue
Normal file
93
src/pages/account/accountDataPage/paymentsCard.vue
Normal file
@@ -0,0 +1,93 @@
|
||||
<script setup lang="ts">
|
||||
import cardView from '@/components/basics/cardView.vue';
|
||||
import { useAccountStore } from '@/stores/account.store';
|
||||
import outlinedButton from '@/components/basics/outlinedButton.vue';
|
||||
import { PaymentModel } from '@/data/models/user/paymentModel';
|
||||
import { getIbanRules, getStringRules } from '@/scripts/validationRules';
|
||||
import cardViewOneLine from '@/components/basics/cardViewOneLine.vue';
|
||||
|
||||
const accountStore = useAccountStore()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<card-view
|
||||
icon="mdi-currency-usd"
|
||||
:title="$t('account.userData.payment')"
|
||||
>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<card-view-one-line
|
||||
color="amber"
|
||||
prepend-icon="mdi-alert"
|
||||
:title="$t('account.noRealPaymentsNeeded')"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row v-if="accountStore.userAccount.payments.length > 0">
|
||||
<v-col>
|
||||
<v-expansion-panels>
|
||||
<v-expansion-panel
|
||||
v-for="payment in accountStore.userAccount.payments"
|
||||
color="primary"
|
||||
>
|
||||
<template #title>
|
||||
{{ payment.bankName }}
|
||||
</template>
|
||||
<template #text>
|
||||
<v-row class="pt-5">
|
||||
<v-col>
|
||||
<v-text-field
|
||||
:label="$t('account.userData.bankName')"
|
||||
v-model="payment.bankName"
|
||||
:rules="getStringRules()"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col>
|
||||
<v-text-field
|
||||
:label="$t('account.userData.iban')"
|
||||
v-model="payment.iban"
|
||||
:rules="getIbanRules()"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row>
|
||||
<v-col class="d-flex justify-center align-center">
|
||||
<outlined-button
|
||||
@click="accountStore.removePayment(payment)"
|
||||
color="red"
|
||||
prepend-icon="mdi-delete"
|
||||
>
|
||||
{{ $t('misc.actions.remove') }}
|
||||
</outlined-button>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
</v-expansion-panel>
|
||||
</v-expansion-panels>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row v-else>
|
||||
<v-col>
|
||||
<v-empty-state
|
||||
:title="$t('account.noPayments')"
|
||||
icon="mdi-currency-usd-off"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
|
||||
|
||||
<template #actions>
|
||||
<outlined-button
|
||||
@click="accountStore.userAccount.payments.push(new PaymentModel())"
|
||||
prepend-icon="mdi-plus"
|
||||
color="green"
|
||||
>
|
||||
{{ $t('misc.actions.add') }}
|
||||
</outlined-button>
|
||||
</template>
|
||||
</card-view>
|
||||
</template>
|
||||
60
src/pages/account/accountHomePage/index.vue
Normal file
60
src/pages/account/accountHomePage/index.vue
Normal file
@@ -0,0 +1,60 @@
|
||||
<script setup lang="ts">
|
||||
import { useAccountStore } from '@/stores/account.store';
|
||||
import cardView from '@/components/basics/cardView.vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
const accountStore = useAccountStore()
|
||||
const router = useRouter()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-container max-width="1000">
|
||||
<v-row>
|
||||
<v-col>
|
||||
<card-view
|
||||
:title="$t('misc.greeting', { msg: accountStore.userAccount.username })"
|
||||
icon="mdi-hand-wave"
|
||||
>
|
||||
<v-container>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<card-view
|
||||
:title="$t('order.order', 2)"
|
||||
icon="mdi-basket-check"
|
||||
@click="router.push('/account/orders')"
|
||||
>
|
||||
{{ $t('order.ordersDescription') }}
|
||||
</card-view>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row>
|
||||
<v-col>
|
||||
<card-view
|
||||
:title="$t('account.accountManagement')"
|
||||
icon="mdi-account"
|
||||
@click="router.push('/account/data')"
|
||||
>
|
||||
{{ $t('account.accountManagementDescription') }}
|
||||
</card-view>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row>
|
||||
<v-col>
|
||||
<card-view
|
||||
:title="$t('account.logout.logout')"
|
||||
icon="mdi-logout"
|
||||
@click="accountStore.logout(); router.push('/account/login')"
|
||||
>
|
||||
{{ $t('account.logout.logoutDescription') }}
|
||||
</card-view>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</card-view>
|
||||
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</template>
|
||||
27
src/pages/account/loginPage/index.vue
Normal file
27
src/pages/account/loginPage/index.vue
Normal file
@@ -0,0 +1,27 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import loginForm from './loginForm.vue';
|
||||
import registerForm from './registerForm.vue';
|
||||
|
||||
const showRegisterCard = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-container max-width="500">
|
||||
<v-expand-transition>
|
||||
<v-row v-if="!showRegisterCard">
|
||||
<v-col>
|
||||
<login-form v-model:show-register-card="showRegisterCard" />
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-expand-transition>
|
||||
|
||||
<v-expand-transition>
|
||||
<v-row v-if="showRegisterCard">
|
||||
<v-col>
|
||||
<register-form v-model:show-register-card="showRegisterCard" />
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-expand-transition>
|
||||
</v-container>
|
||||
</template>
|
||||
73
src/pages/account/loginPage/loginForm.vue
Normal file
73
src/pages/account/loginPage/loginForm.vue
Normal file
@@ -0,0 +1,73 @@
|
||||
<script setup lang="ts">
|
||||
import cardView from '@/components/basics/cardView.vue';
|
||||
import outlinedButton from '@/components/basics/outlinedButton.vue';
|
||||
import { useAccountStore } from '@/stores/account.store';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
const accountStore = useAccountStore()
|
||||
const showRegisterCard = defineModel("showRegisterCard", { type: Boolean, default: false })
|
||||
const router = useRouter()
|
||||
|
||||
async function startLogin() {
|
||||
accountStore.login()
|
||||
.then(result => {
|
||||
if (accountStore.userAccountToken != "") {
|
||||
router.push("/account/home")
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<card-view
|
||||
:title="$t('account.login.login')"
|
||||
icon="mdi-login"
|
||||
max-width="600"
|
||||
>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-text-field
|
||||
:label="$t('account.userData.username')"
|
||||
prepend-icon="mdi-account"
|
||||
v-model="accountStore.loginData.username"
|
||||
variant="outlined"
|
||||
clearable
|
||||
@keyup.enter="startLogin"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-text-field
|
||||
:label="$t('account.userData.password')"
|
||||
prepend-icon="mdi-key"
|
||||
type="password"
|
||||
variant="outlined"
|
||||
v-model="accountStore.loginData.password"
|
||||
clearable
|
||||
@keyup.enter="startLogin"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<template #actions>
|
||||
<outlined-button
|
||||
@click="showRegisterCard = true"
|
||||
prepend-icon="mdi-plus"
|
||||
:loading="accountStore.fetchInProgress"
|
||||
>
|
||||
{{ $t('account.register') }}
|
||||
</outlined-button>
|
||||
|
||||
<outlined-button
|
||||
append-icon="mdi-arrow-right"
|
||||
@click="startLogin"
|
||||
:loading="accountStore.fetchInProgress"
|
||||
color="green"
|
||||
>
|
||||
{{ $t('account.login.login') }}
|
||||
</outlined-button>
|
||||
</template>
|
||||
</card-view>
|
||||
</template>
|
||||
88
src/pages/account/loginPage/registerForm.vue
Normal file
88
src/pages/account/loginPage/registerForm.vue
Normal file
@@ -0,0 +1,88 @@
|
||||
<script setup lang="ts">
|
||||
import cardView from '@/components/basics/cardView.vue';
|
||||
import outlinedButton from '@/components/basics/outlinedButton.vue';
|
||||
import { useAccountStore } from '@/stores/account.store';
|
||||
import { getEmailRules, getPasswordRules, getStringRules } from '@/scripts/validationRules';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
const showRegisterCard = defineModel("showRegisterCard", { type: Boolean, default: false })
|
||||
const accountStore = useAccountStore()
|
||||
const router = useRouter()
|
||||
|
||||
async function registerAccount() {
|
||||
accountStore.registerAccount()
|
||||
.then(result => {
|
||||
if (result) {
|
||||
showRegisterCard.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<card-view
|
||||
:title="$t('account.register')"
|
||||
icon="mdi-account-plus"
|
||||
>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-text-field
|
||||
:label="$t('account.userData.username')"
|
||||
prepend-icon="mdi-account"
|
||||
v-model="accountStore.registerData.username"
|
||||
clearable
|
||||
hide-details
|
||||
variant="outlined"
|
||||
:rules="getStringRules()"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-text-field
|
||||
:label="$t('account.userData.password')"
|
||||
prepend-icon="mdi-key"
|
||||
type="password"
|
||||
v-model="accountStore.registerData.password"
|
||||
clearable
|
||||
hide-details
|
||||
variant="outlined"
|
||||
:rules="getPasswordRules()"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-text-field
|
||||
:label="$t('account.userData.email')"
|
||||
prepend-icon="mdi-mail"
|
||||
v-model="accountStore.registerData.email"
|
||||
:rules="getEmailRules()"
|
||||
variant="outlined"
|
||||
hide-details
|
||||
clearable
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<template #actions>
|
||||
<outlined-button
|
||||
prepend-icon="mdi-arrow-left"
|
||||
@click="showRegisterCard = false"
|
||||
:disabled="accountStore.fetchInProgress"
|
||||
>
|
||||
{{ $t('account.login.backToLogin') }}
|
||||
</outlined-button>
|
||||
|
||||
<outlined-button
|
||||
prepend-icon="mdi-account-plus"
|
||||
@click="registerAccount"
|
||||
:loading="accountStore.fetchInProgress"
|
||||
>
|
||||
{{ $t('account.register') }}
|
||||
</outlined-button>
|
||||
</template>
|
||||
</card-view>
|
||||
</template>
|
||||
48
src/pages/account/ordersPage/index.vue
Normal file
48
src/pages/account/ordersPage/index.vue
Normal file
@@ -0,0 +1,48 @@
|
||||
<script setup lang="ts">
|
||||
import { useAccountStore } from '@/stores/account.store';
|
||||
import orderItem from './orderItem.vue';
|
||||
import accountSubPageLayout from '@/layouts/accountSubPageLayout.vue';
|
||||
import circularProgressIndeterminate from '@/components/basics/circularProgressIndeterminate.vue';
|
||||
import { useOrderStore } from '@/stores/order.store';
|
||||
|
||||
const accountStore = useAccountStore()
|
||||
const orderStore = useOrderStore()
|
||||
|
||||
orderStore.getOrdersOfAccount(accountStore.userAccount)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<account-sub-page-layout>
|
||||
<!-- During fetching state -->
|
||||
<v-row
|
||||
v-if="orderStore.fetchInProgress"
|
||||
>
|
||||
<v-col class="text-center">
|
||||
<circular-progress-indeterminate />
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<!-- Display all orders -->
|
||||
<v-row
|
||||
v-else-if="orderStore.orders.length > 0"
|
||||
v-for="order in orderStore.orders"
|
||||
>
|
||||
<v-col>
|
||||
<order-item
|
||||
:order="order"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<!-- No orders -->
|
||||
<v-row v-else>
|
||||
<v-col>
|
||||
<v-empty-state
|
||||
icon="mdi-basket-off"
|
||||
:title="$t('order.noOrders')"
|
||||
:text="$t('order.noOrdersText')"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</account-sub-page-layout>
|
||||
</template>
|
||||
58
src/pages/account/ordersPage/orderItem.vue
Normal file
58
src/pages/account/ordersPage/orderItem.vue
Normal file
@@ -0,0 +1,58 @@
|
||||
<script setup lang="ts">
|
||||
import cardView from '@/components/basics/cardView.vue';
|
||||
import ticketListItem from '@/components/pageParts/ticketListItem.vue';
|
||||
import { OrderApiModel } from '@/data/models/apiEndpoints/orderApiModel';
|
||||
import moment from 'moment';
|
||||
|
||||
defineProps({
|
||||
order: OrderApiModel
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<card-view
|
||||
:title="$t('order.orderedAt') + ' ' + moment(order.orderedAt).format('DD.MM.YY, HH:mm') + ' ' + $t('order.oclock')"
|
||||
variant="outlined"
|
||||
>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-card variant="outlined" class="ml-5 pa-3">
|
||||
<div class="text-h6">
|
||||
<v-icon icon="mdi-home" />
|
||||
{{ $t('account.userData.address', 1) }}
|
||||
</div>
|
||||
|
||||
<div class="pl-9">{{ order.address.street }} {{ order.address.houseNumber }}</div>
|
||||
<div class="pl-9">{{ order.address.postalCode }} {{ order.address.city }}</div>
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
||||
<v-col>
|
||||
<v-card variant="outlined" class="mr-5 pa-3">
|
||||
<div class="text-h6">
|
||||
<v-icon icon="mdi-currency-usd" />
|
||||
{{ $t('account.userData.payment', 1) }}
|
||||
</div>
|
||||
<div class="pl-9">{{ order.payment.bankName }}</div>
|
||||
<div class="pl-9">{{ order.payment.iban }}</div>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row v-for="ticket of order.tickets">
|
||||
<v-col>
|
||||
<ticket-list-item
|
||||
:concert="ticket.concert"
|
||||
:event="ticket.concert.name"
|
||||
:band="ticket.concert.band"
|
||||
:location="ticket.concert.location"
|
||||
:city="ticket.concert.location.city"
|
||||
:image="ticket.concert.image"
|
||||
:seat-nr="ticket.seat.seatNr"
|
||||
:seat-group="ticket.seat.seatRow.seatGroup.name"
|
||||
:seat-row="ticket.seat.seatRow.row"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</card-view>
|
||||
</template>
|
||||
48
src/pages/admin/accountsAdminPage/index.vue
Normal file
48
src/pages/admin/accountsAdminPage/index.vue
Normal file
@@ -0,0 +1,48 @@
|
||||
<script setup lang="ts">
|
||||
import { useAccountStore } from '@/stores/account.store';
|
||||
import adminDataLayout from '@/layouts/adminDataLayout.vue';
|
||||
import { useFeedbackStore } from '@/stores/feedback.store';
|
||||
|
||||
const accountStore = useAccountStore()
|
||||
const feedbackStore = useFeedbackStore()
|
||||
|
||||
const headers = [
|
||||
{ title: feedbackStore.i18n.t('account.userData.username'), value: "username" },
|
||||
{ title: feedbackStore.i18n.t('account.userData.email'), value: "email" },
|
||||
{ title: feedbackStore.i18n.t('account.userData.firstName'), value: "firstName" },
|
||||
{ title: feedbackStore.i18n.t('account.userData.lastName'), value: "lastName" },
|
||||
{ title: feedbackStore.i18n.t('account.accountRole'), value: "accountRole.name" },
|
||||
{ title: "", value: "edit", width: 130 }
|
||||
]
|
||||
|
||||
accountStore.getAllAccounts()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<admin-data-layout
|
||||
:add-button-string="$t('account.addNewAccount')"
|
||||
:fetch-in-progress="accountStore.fetchInProgress"
|
||||
>
|
||||
<v-data-table
|
||||
:items="accountStore.accounts"
|
||||
:loading="accountStore.fetchInProgress"
|
||||
:headers="headers"
|
||||
>
|
||||
<template #item.edit="{ item }">
|
||||
<v-btn
|
||||
icon="mdi-pencil"
|
||||
variant="plain"
|
||||
color="orange"
|
||||
@click="accountStore.editAccount(item)"
|
||||
/>
|
||||
|
||||
<v-btn
|
||||
icon="mdi-delete"
|
||||
variant="plain"
|
||||
color="red"
|
||||
@click="accountStore.deleteAccount(item)"
|
||||
/>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</admin-data-layout>
|
||||
</template>
|
||||
104
src/pages/admin/bandsAdminPage/bandEditDialog.vue
Normal file
104
src/pages/admin/bandsAdminPage/bandEditDialog.vue
Normal file
@@ -0,0 +1,104 @@
|
||||
<script setup lang="ts">
|
||||
import actionDialog from '@/components/basics/actionDialog.vue';
|
||||
import OutlinedButton from '@/components/basics/outlinedButton.vue';
|
||||
import { GenreModel } from '@/data/models/acts/genreModel';
|
||||
import { useBandStore } from '@/stores/band.store';
|
||||
import { useGenreStore } from '@/stores/genre.store';
|
||||
|
||||
const bandStore = useBandStore()
|
||||
const genreStore = useGenreStore()
|
||||
|
||||
function itemProps(item: GenreModel) {
|
||||
return {
|
||||
title: item.name
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<action-dialog
|
||||
v-model="bandStore.showEditDialog"
|
||||
:title="$t('band.editBand')"
|
||||
icon="mdi-pencil"
|
||||
>
|
||||
<v-container>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-text-field
|
||||
:label="$t('band.name')"
|
||||
v-model="bandStore.band.name"
|
||||
variant="outlined"
|
||||
hide-details
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<v-col>
|
||||
<v-text-field
|
||||
:label="$t('band.foundingYear')"
|
||||
v-model="bandStore.band.foundingYear"
|
||||
variant="outlined"
|
||||
hide-details
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<v-col>
|
||||
<v-select
|
||||
:label="$t('band.genre', 2)"
|
||||
v-model="bandStore.band.genres"
|
||||
:items="genreStore.genres"
|
||||
:item-props="itemProps"
|
||||
variant="outlined"
|
||||
hide-details
|
||||
chips
|
||||
multiple
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-textarea
|
||||
:label="$t('band.descriptionDe')"
|
||||
v-model="bandStore.band.descriptionDe"
|
||||
variant="outlined"
|
||||
hide-details
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<v-col>
|
||||
<v-textarea
|
||||
:label="$t('band.descriptionEn')"
|
||||
v-model="bandStore.band.descriptionEn"
|
||||
variant="outlined"
|
||||
hide-details
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-img
|
||||
:src="bandStore.band.logo"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col>
|
||||
<v-img
|
||||
max-width="300"
|
||||
:src="bandStore.band.imageMembers"
|
||||
placeholder=""
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
|
||||
<template #actions>
|
||||
<outlined-button
|
||||
color="green"
|
||||
@click="bandStore.saveBand"
|
||||
:loading="bandStore.fetchInProgress"
|
||||
>
|
||||
{{ $t('misc.actions.save') }}
|
||||
</outlined-button>
|
||||
</template>
|
||||
</action-dialog>
|
||||
</template>
|
||||
78
src/pages/admin/bandsAdminPage/index.vue
Normal file
78
src/pages/admin/bandsAdminPage/index.vue
Normal file
@@ -0,0 +1,78 @@
|
||||
<script setup lang="ts">
|
||||
import { useBandStore } from '@/stores/band.store';
|
||||
import bandEditDialog from './bandEditDialog.vue';
|
||||
import adminDataLayout from '@/layouts/adminDataLayout.vue';
|
||||
import { useFeedbackStore } from '@/stores/feedback.store';
|
||||
|
||||
const bandStore = useBandStore()
|
||||
const feedbackStore = useFeedbackStore()
|
||||
|
||||
const headers = [
|
||||
{ title: feedbackStore.i18n.t('band.name'), value: "name" },
|
||||
{ title: feedbackStore.i18n.t('band.foundingYear'), value: "foundingYear" },
|
||||
{ title: feedbackStore.i18n.t('band.genre', 2), value: "genres" },
|
||||
{ title: feedbackStore.i18n.t('band.logo', 2), value: "logo" },
|
||||
{ title: feedbackStore.i18n.t('band.imageMember', 2), value: "imageMembers" },
|
||||
{ title: feedbackStore.i18n.t('band.image', 2), value: "images" },
|
||||
{ title: feedbackStore.i18n.t('concert.concert', 2), value: "nrOfConcerts" },
|
||||
{ title: "", value: "edit", width: 130 }
|
||||
]
|
||||
|
||||
bandStore.getBands()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<admin-data-layout
|
||||
:add-button-string="$t('band.addNewBand')"
|
||||
:fetch-in-progress="bandStore.fetchInProgress"
|
||||
:on-add-click="() => bandStore.newBand()"
|
||||
>
|
||||
<v-data-table
|
||||
:items="bandStore.bands"
|
||||
:headers="headers"
|
||||
:loading="bandStore.fetchInProgress"
|
||||
>
|
||||
<template #item.genres="{ item }">
|
||||
<v-chip v-for="genre of item.genres" class="mx-1">
|
||||
{{ genre.name }}
|
||||
</v-chip>
|
||||
</template>
|
||||
|
||||
<template #item.logo="{ item }">
|
||||
<v-icon
|
||||
:icon="item.logo != '' ? 'mdi-check' : 'mdi-close'"
|
||||
:color="item.logo != '' ? 'green' : 'red'"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #item.imageMembers="{ item }">
|
||||
<v-icon
|
||||
:icon="item.imageMembers != '' ? 'mdi-check' : 'mdi-close'"
|
||||
:color="item.imageMembers != '' ? 'green' : 'red'"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #item.images="{ item }">
|
||||
{{ item.images.length }} {{ $t('band.image', item.images.length) }}
|
||||
</template>
|
||||
|
||||
<template #item.edit="{ item }">
|
||||
<v-btn
|
||||
icon="mdi-pencil"
|
||||
variant="plain"
|
||||
color="orange"
|
||||
@click="bandStore.editBand(item.name)"
|
||||
/>
|
||||
|
||||
<v-btn
|
||||
icon="mdi-delete"
|
||||
variant="plain"
|
||||
color="red"
|
||||
@click="bandStore.deleteBand(item.id)"
|
||||
/>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</admin-data-layout>
|
||||
|
||||
<band-edit-dialog />
|
||||
</template>
|
||||
77
src/pages/admin/concertsAdminPage/index.vue
Normal file
77
src/pages/admin/concertsAdminPage/index.vue
Normal file
@@ -0,0 +1,77 @@
|
||||
<script setup lang="ts">
|
||||
import { useBandStore } from '@/stores/band.store';
|
||||
import { useConcertStore } from '@/stores/concert.store';
|
||||
import { useFeedbackStore } from '@/stores/feedback.store';
|
||||
import adminDataLayout from '@/layouts/adminDataLayout.vue';
|
||||
import moment from 'moment';
|
||||
|
||||
const concertStore = useConcertStore()
|
||||
const bandStore = useBandStore()
|
||||
const feedbackStore = useFeedbackStore()
|
||||
|
||||
const headers = [
|
||||
{ title: feedbackStore.i18n.t('concert.date'), value: "date" },
|
||||
{ title: feedbackStore.i18n.t('concert.name'), value: "name" },
|
||||
{ title: feedbackStore.i18n.t('band.name'), value: "band.name" },
|
||||
{ title: feedbackStore.i18n.t('location.name'), value: "location.name" },
|
||||
{ title: feedbackStore.i18n.t('concert.inStock'), value: "inStock" },
|
||||
{ title: feedbackStore.i18n.t('concert.offered'), value: "offered" },
|
||||
{ title: feedbackStore.i18n.t('concert.image'), value: "image" },
|
||||
{ title: feedbackStore.i18n.t('concert.price'), value: "price" },
|
||||
{ title: "", value: "edit", width: 130 }
|
||||
]
|
||||
|
||||
concertStore.getConcerts()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<admin-data-layout
|
||||
:add-button-string="$t('concert.addNewConcert')"
|
||||
:fetch-in-progress="concertStore.fetchInProgress"
|
||||
:on-add-click="() => concertStore.newConcert()"
|
||||
>
|
||||
<v-data-table
|
||||
:items="concertStore.concerts"
|
||||
:loading="concertStore.fetchInProgress"
|
||||
:headers="headers"
|
||||
>
|
||||
<template #item.date="{ item }">
|
||||
{{ moment(item.date).format("dd, DD.MM.YYYY") }}
|
||||
</template>
|
||||
|
||||
<template #item.price="{ item }">
|
||||
{{ item.price.toFixed(2) }} €
|
||||
</template>
|
||||
|
||||
<template #item.image="{ item }">
|
||||
<v-icon
|
||||
:icon="item.image != '' ? 'mdi-check' : 'mdi-close'"
|
||||
:color="item.image != '' ? 'green' : 'red'"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #item.offered="{ item }">
|
||||
<v-icon
|
||||
:icon="item.offered ? 'mdi-check' : 'mdi-close'"
|
||||
:color="item.offered ? 'green' : 'red'"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #item.edit="{ item }">
|
||||
<v-btn
|
||||
icon="mdi-pencil"
|
||||
variant="plain"
|
||||
color="orange"
|
||||
@click="concertStore.editConcert(item)"
|
||||
/>
|
||||
|
||||
<v-btn
|
||||
icon="mdi-delete"
|
||||
variant="plain"
|
||||
color="red"
|
||||
@click="concertStore.deleteConcert(item)"
|
||||
/>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</admin-data-layout>
|
||||
</template>
|
||||
50
src/pages/admin/dashboardPage/dashboardCard.vue
Normal file
50
src/pages/admin/dashboardPage/dashboardCard.vue
Normal file
@@ -0,0 +1,50 @@
|
||||
<script setup lang="ts">
|
||||
import cardView from '@/components/basics/cardView.vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import outlinedButton from '@/components/basics/outlinedButton.vue';
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
defineProps({
|
||||
title: String,
|
||||
icon: String,
|
||||
firstLine: String,
|
||||
secondLine: String,
|
||||
buttonRoute: String,
|
||||
loading: Boolean
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-col cols="12" md="6" lg="4">
|
||||
<card-view
|
||||
:title="title"
|
||||
:icon="icon"
|
||||
>
|
||||
<v-skeleton-loader
|
||||
type="heading"
|
||||
:loading="loading"
|
||||
class="text-h4 d-flex justify-center"
|
||||
>
|
||||
{{ firstLine }}
|
||||
</v-skeleton-loader>
|
||||
|
||||
<v-skeleton-loader
|
||||
type="text"
|
||||
:loading="loading"
|
||||
class="text-h6 text-disabled d-flex justify-center"
|
||||
>
|
||||
{{ secondLine }}
|
||||
</v-skeleton-loader>
|
||||
|
||||
<template #actions>
|
||||
<outlined-button
|
||||
@click="router.push(buttonRoute)"
|
||||
:loading="loading"
|
||||
>
|
||||
{{ $t('misc.actions.more') }}
|
||||
</outlined-button>
|
||||
</template>
|
||||
</card-view>
|
||||
</v-col>
|
||||
</template>
|
||||
119
src/pages/admin/dashboardPage/index.vue
Normal file
119
src/pages/admin/dashboardPage/index.vue
Normal file
@@ -0,0 +1,119 @@
|
||||
<script setup lang="ts">
|
||||
import { useConcertStore } from '@/stores/concert.store';
|
||||
import { useBandStore } from '@/stores/band.store';
|
||||
import { useAccountStore } from '@/stores/account.store';
|
||||
import { useLocationStore } from '@/stores/location.store';
|
||||
import { useGenreStore } from '@/stores/genre.store';
|
||||
import { usePreferencesStore } from '@/stores/preferences.store';
|
||||
import dashboardCard from './dashboardCard.vue';
|
||||
import { useOrderStore } from '@/stores/order.store';
|
||||
import { useFilesStore } from '@/stores/files.store';
|
||||
|
||||
const concertStore = useConcertStore()
|
||||
const bandStore = useBandStore()
|
||||
const accountStore = useAccountStore()
|
||||
const genreStore = useGenreStore()
|
||||
const locationStore = useLocationStore()
|
||||
const preferencesStore = usePreferencesStore()
|
||||
const orderStore = useOrderStore()
|
||||
const filesStore = useFilesStore()
|
||||
|
||||
filesStore.getStaticFolders()
|
||||
bandStore.getBands()
|
||||
locationStore.getLocations()
|
||||
genreStore.getGenres()
|
||||
accountStore.getAllAccounts()
|
||||
concertStore.getConcerts()
|
||||
orderStore.getAllOrders()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-container>
|
||||
<v-row>
|
||||
<dashboard-card
|
||||
:title="$t('band.band', 2)"
|
||||
icon="mdi-guitar-electric"
|
||||
:first-line="bandStore.bands.length + ' ' + $t('band.band', 2)"
|
||||
:second-line="bandStore.bands.reduce((counter, band) => {
|
||||
return band.concerts.length == 0 ? counter += 1 : counter
|
||||
}, 0) + ' ' + $t('band.withoutConcert', 2)"
|
||||
button-route="/admin/bands"
|
||||
:loading="bandStore.fetchInProgress"
|
||||
/>
|
||||
|
||||
<dashboard-card
|
||||
:title="$t('concert.concert', 2)"
|
||||
icon="mdi-ticket"
|
||||
:first-line="concertStore.concerts.length + ' ' + $t('concert.concert', 2)"
|
||||
:second-line="concertStore.concerts.reduce((counter, obj) => {
|
||||
if (obj.inStock == 0) {
|
||||
counter += 1
|
||||
}
|
||||
|
||||
return counter
|
||||
}, 0) + ' ' + $t('concert.concertSoldOut')"
|
||||
button-route="/admin/concerts"
|
||||
:loading="concertStore.fetchInProgress"
|
||||
/>
|
||||
|
||||
|
||||
<dashboard-card
|
||||
:title="$t('location.location', 2)"
|
||||
icon="mdi-city"
|
||||
:first-line="locationStore.locations.length + ' ' + $t('location.location', 2)"
|
||||
:second-line="locationStore.locations.reduce((city, obj) => {
|
||||
city[obj.city.name] =
|
||||
city[obj.city.name] === undefined ? city.push(obj.city.name) : city[obj.city.name] += 1
|
||||
|
||||
return city
|
||||
}, []).length + ' ' + $t('location.city', 2)"
|
||||
button-route="/admin/locations"
|
||||
:loading="locationStore.fetchInProgress"
|
||||
/>
|
||||
|
||||
<dashboard-card
|
||||
:title="$t('account.account', 2)"
|
||||
icon="mdi-account"
|
||||
:first-line="accountStore.accounts.length + ' ' + $t('account.account', 2)"
|
||||
:second-line="accountStore.accounts.reduce((counter, obj) => {
|
||||
return obj.accountRole.privilegeAdminPanel ? counter += 1 : counter
|
||||
}, 0) + ' ' + $t('account.administrator', 2)"
|
||||
button-route="/admin/accounts"
|
||||
:loading="accountStore.fetchInProgress"
|
||||
/>
|
||||
|
||||
<dashboard-card
|
||||
:title="$t('band.genre', 2)"
|
||||
icon="mdi-music-clef-treble"
|
||||
:first-line="genreStore.genres.length + ' ' + $t('band.genre', 2)"
|
||||
:second-line="genreStore.genres.reduce((counter, obj) => {
|
||||
return obj.bands.length == 0 ? counter += 1 : counter
|
||||
}, 0) + ' ' + $t('genre.withoutBand', 2)"
|
||||
button-route="/admin/genres"
|
||||
:loading="genreStore.fetchInProgress"
|
||||
/>
|
||||
|
||||
<dashboard-card
|
||||
:title="$t('misc.file', 2)"
|
||||
icon="mdi-file"
|
||||
:first-line="filesStore.staticFolders.reduce((counter, obj) => {
|
||||
return counter + obj.nrOfItems
|
||||
}, 0) + ' ' + $t('misc.file', 2)"
|
||||
:second-line="filesStore.staticFolders.length + ' ' + $t('misc.folder', 2)"
|
||||
button-route="/admin/files"
|
||||
:loading="preferencesStore.fetchInProgress"
|
||||
/>
|
||||
|
||||
<dashboard-card
|
||||
:title="$t('order.order', 2)"
|
||||
icon="mdi-basket"
|
||||
:first-line="orderStore.orders.length + ' ' + $t('order.order', 2)"
|
||||
:second-line="orderStore.orders.reduce((counter, obj) => {
|
||||
return !obj.shipped ? counter += 1 : counter
|
||||
}, 0) + ' ' + $t('order.notShipped')"
|
||||
button-route="/admin/orders"
|
||||
:loading="orderStore.fetchInProgress"
|
||||
/>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</template>
|
||||
64
src/pages/admin/filesAdminPage/fileUploadDialog.vue
Normal file
64
src/pages/admin/filesAdminPage/fileUploadDialog.vue
Normal file
@@ -0,0 +1,64 @@
|
||||
<script setup lang="ts">
|
||||
import actionDialog from '@/components/basics/actionDialog.vue';
|
||||
import outlinedButton from '@/components/basics/outlinedButton.vue';
|
||||
import { useFilesStore } from '@/stores/files.store';
|
||||
import { ref } from 'vue';
|
||||
|
||||
const filesStore = useFilesStore()
|
||||
|
||||
const test = ref()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<action-dialog
|
||||
v-model="filesStore.showFileUploadDialog"
|
||||
:title="$t('misc.uploadFile')"
|
||||
icon="mdi-file"
|
||||
max-width="800"
|
||||
>
|
||||
<v-form :model-value="test">
|
||||
<v-container>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-file-input
|
||||
v-model="filesStore.fileUpload"
|
||||
clearable
|
||||
:label="$t('misc.chooseFile')"
|
||||
:disabled="filesStore.fetchInProgress"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-radio-group
|
||||
v-model="filesStore.fileUploadDir"
|
||||
:label="$t('misc.chooseDestinationFolder')"
|
||||
:disabled="filesStore.fetchInProgress"
|
||||
>
|
||||
<v-radio
|
||||
v-for="folder of filesStore.staticFolders"
|
||||
:label="folder.name + '/'"
|
||||
:value="folder.name"
|
||||
/>
|
||||
</v-radio-group>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
|
||||
<v-btn type="submit">Submit</v-btn>
|
||||
</v-form>
|
||||
|
||||
<template #actions>
|
||||
<outlined-button
|
||||
@click="filesStore.uploadFile"
|
||||
prepend-icon="mdi-file-upload"
|
||||
color="green"
|
||||
:disabled="filesStore.fileUploadDir.length == 0 || filesStore.fileUpload == undefined"
|
||||
:loading="filesStore.fetchInProgress"
|
||||
>
|
||||
{{ $t('misc.upload') }}
|
||||
</outlined-button>
|
||||
</template>
|
||||
</action-dialog>
|
||||
</template>
|
||||
64
src/pages/admin/filesAdminPage/index.vue
Normal file
64
src/pages/admin/filesAdminPage/index.vue
Normal file
@@ -0,0 +1,64 @@
|
||||
<script setup lang="ts">
|
||||
import adminDataLayout from '@/layouts/adminDataLayout.vue';
|
||||
import { ref } from 'vue';
|
||||
import FileUploadDialog from './fileUploadDialog.vue';
|
||||
import { useFilesStore } from '@/stores/files.store';
|
||||
|
||||
const filesStore = useFilesStore()
|
||||
const showPreviewDialog = ref(false)
|
||||
const previewFile = ref("")
|
||||
|
||||
filesStore.getStaticFolders()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<admin-data-layout
|
||||
:add-button-string="$t('misc.uploadFile')"
|
||||
:fetch-in-progress="filesStore.fetchInProgress"
|
||||
:on-add-click="() => { filesStore.showFileUploadDialog = true }"
|
||||
>
|
||||
<v-row >
|
||||
<v-col cols="2" class="border">
|
||||
<v-list>
|
||||
<v-list-item
|
||||
v-for="folder of filesStore.staticFolders"
|
||||
:key="folder.name"
|
||||
:value="folder"
|
||||
:title="folder.name + '/'"
|
||||
@click="filesStore.selectedFolder = folder; filesStore.getStaticFiles()"
|
||||
/>
|
||||
</v-list>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="4" class="border">
|
||||
<v-skeleton-loader
|
||||
:loading="filesStore.fetchInProgress"
|
||||
type="list-item-two-line"
|
||||
>
|
||||
<v-list max-height="800" class="w-100">
|
||||
<v-list-item
|
||||
v-for="file of filesStore.staticFiles"
|
||||
:title="file.name"
|
||||
:value="file.name"
|
||||
:subtitle="Math.round(file.size / 1024) + ' KB'"
|
||||
@click="() => { filesStore.selectedFile = file }"
|
||||
/>
|
||||
</v-list>
|
||||
</v-skeleton-loader>
|
||||
</v-col>
|
||||
|
||||
<v-col class="border">
|
||||
<v-img
|
||||
v-if="filesStore.selectedFile != undefined"
|
||||
:src="filesStore.selectedFile.url" max-height="400" />
|
||||
</v-col>
|
||||
</v-row>
|
||||
</admin-data-layout>
|
||||
|
||||
<file-preview-dialog
|
||||
v-model:show-dialog="showPreviewDialog"
|
||||
:url="previewFile"
|
||||
/>
|
||||
|
||||
<file-upload-dialog />
|
||||
</template>
|
||||
47
src/pages/admin/genresAdminPage/genreEditDialog.vue
Normal file
47
src/pages/admin/genresAdminPage/genreEditDialog.vue
Normal file
@@ -0,0 +1,47 @@
|
||||
<script setup lang="ts">
|
||||
import actionDialog from '@/components/basics/actionDialog.vue';
|
||||
import outlinedButton from '@/components/basics/outlinedButton.vue';
|
||||
import { getStringRules } from '@/scripts/validationRules';
|
||||
import { useGenreStore } from '@/stores/genre.store';
|
||||
import { ref } from 'vue';
|
||||
|
||||
const valid = ref(false)
|
||||
|
||||
const genreStore = useGenreStore()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<action-dialog
|
||||
v-model="genreStore.showEditDialog"
|
||||
:title="$t('band.editGenre')"
|
||||
icon="mdi-pencil"
|
||||
max-width="500"
|
||||
>
|
||||
<v-form v-model="valid">
|
||||
<v-container>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-text-field
|
||||
:label="$t('band.genre')"
|
||||
v-model="genreStore.genre.name"
|
||||
variant="outlined"
|
||||
hide-details
|
||||
:rules="getStringRules(3)"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</v-form>
|
||||
|
||||
<template #actions>
|
||||
<outlined-button
|
||||
color="green"
|
||||
@click="genreStore.saveGenre"
|
||||
:disabled="!valid"
|
||||
:loading="genreStore.fetchInProgress"
|
||||
>
|
||||
{{ $t('misc.actions.save') }}
|
||||
</outlined-button>
|
||||
</template>
|
||||
</action-dialog>
|
||||
</template>
|
||||
54
src/pages/admin/genresAdminPage/index.vue
Normal file
54
src/pages/admin/genresAdminPage/index.vue
Normal file
@@ -0,0 +1,54 @@
|
||||
<script setup lang="ts">
|
||||
import adminDataLayout from '@/layouts/adminDataLayout.vue';
|
||||
import genreEditDialog from './genreEditDialog.vue';
|
||||
import { useGenreStore } from '@/stores/genre.store';
|
||||
|
||||
const genreStore = useGenreStore()
|
||||
|
||||
const headers = [
|
||||
{ title: "Name", value: "name" },
|
||||
{ title: "Bands", value: "bands" },
|
||||
{ title: "", value: "edit", width: 130 }
|
||||
]
|
||||
|
||||
genreStore.getGenres()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<admin-data-layout
|
||||
:add-button-string="$t('band.addNewGenre')"
|
||||
:fetch-in-progress="genreStore.fetchInProgress"
|
||||
:on-add-click="() => { genreStore.newGenre() }"
|
||||
>
|
||||
<v-data-table
|
||||
:loading="genreStore.fetchInProgress"
|
||||
:items="genreStore.genres"
|
||||
:headers="headers"
|
||||
:items-per-page="100"
|
||||
>
|
||||
<template #item.bands="{ item }">
|
||||
<v-chip v-for="band of item.bands" class="mx-1">
|
||||
{{ band.name }}
|
||||
</v-chip>
|
||||
</template>
|
||||
|
||||
<template #item.edit="{ item }">
|
||||
<v-btn
|
||||
icon="mdi-pencil"
|
||||
variant="plain"
|
||||
color="orange"
|
||||
@click="genreStore.editGenre(item)"
|
||||
/>
|
||||
|
||||
<v-btn
|
||||
icon="mdi-delete"
|
||||
variant="plain"
|
||||
color="red"
|
||||
@click="genreStore.deleteGenre(item)"
|
||||
/>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</admin-data-layout>
|
||||
|
||||
<genre-edit-dialog />
|
||||
</template>
|
||||
70
src/pages/admin/locationsAdminPage/index.vue
Normal file
70
src/pages/admin/locationsAdminPage/index.vue
Normal file
@@ -0,0 +1,70 @@
|
||||
<script setup lang="ts">
|
||||
import adminDataLayout from '@/layouts/adminDataLayout.vue';
|
||||
import { useFeedbackStore } from '@/stores/feedback.store';
|
||||
import { useLocationStore } from '@/stores/location.store';
|
||||
|
||||
const locationStore = useLocationStore()
|
||||
const feedbackStore = useFeedbackStore()
|
||||
|
||||
const headers = [
|
||||
{ title: feedbackStore.i18n.t('location.name'), value: "name" },
|
||||
{ title: feedbackStore.i18n.t('location.address'), value: "address" },
|
||||
{ title: feedbackStore.i18n.t('location.imageIndoor'), value: "imageIndoor" },
|
||||
{ title: feedbackStore.i18n.t('location.imageOutdoor'), value: "imageOutdoor" },
|
||||
{ title: feedbackStore.i18n.t('location.layoutNr'), value: "layout" },
|
||||
{ title: feedbackStore.i18n.t('location.capacity'), value: "capacity" },
|
||||
{ title: feedbackStore.i18n.t('location.city'), value: "city" },
|
||||
{ title: feedbackStore.i18n.t('location.nrOfConcerts'), value: "nrOfConcerts" },
|
||||
{ title: "", value: "edit", width: 130 }
|
||||
]
|
||||
|
||||
locationStore.getLocations()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<admin-data-layout
|
||||
:fetch-in-progress="locationStore.fetchInProgress"
|
||||
:add-button-string="$t('location.addLocation')"
|
||||
:on-add-click="() => { locationStore.newLocation() }"
|
||||
>
|
||||
<v-data-table
|
||||
:items="locationStore.locations"
|
||||
:headers="headers"
|
||||
:loading="locationStore.fetchInProgress"
|
||||
>
|
||||
<template #item.imageIndoor="{ item }">
|
||||
<v-icon
|
||||
:icon="item.imageIndoor != '' ? 'mdi-check' : 'mdi-close'"
|
||||
:color="item.imageIndoor != '' ? 'green' : 'red'"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #item.imageOutdoor="{ item }">
|
||||
<v-icon
|
||||
:icon="item.imageOutdoor != '' ? 'mdi-check' : 'mdi-close'"
|
||||
:color="item.imageOutdoor != '' ? 'green' : 'red'"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #item.city="{ item }">
|
||||
{{ item.city.name }}
|
||||
</template>
|
||||
|
||||
<template #item.edit="{ item }">
|
||||
<v-btn
|
||||
icon="mdi-pencil"
|
||||
variant="plain"
|
||||
color="orange"
|
||||
@click="locationStore.editLocation(item)"
|
||||
/>
|
||||
|
||||
<v-btn
|
||||
icon="mdi-delete"
|
||||
variant="plain"
|
||||
color="red"
|
||||
@click="locationStore.deleteLocation(item)"
|
||||
/>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</admin-data-layout>
|
||||
</template>
|
||||
72
src/pages/admin/ordersAdminPage/index.vue
Normal file
72
src/pages/admin/ordersAdminPage/index.vue
Normal file
@@ -0,0 +1,72 @@
|
||||
<script setup lang="ts">
|
||||
import adminDataLayout from '@/layouts/adminDataLayout.vue';
|
||||
import { useOrderStore } from '@/stores/order.store';
|
||||
import moment from 'moment';
|
||||
import OrderDetailDialog from './orderDetailDialog.vue';
|
||||
|
||||
const orderStore = useOrderStore()
|
||||
|
||||
const headers = [
|
||||
{ title: "Account", value: "account.username" },
|
||||
{ title: "Name", value: "account" },
|
||||
{ title: "Bestellt am", value: "orderedAt" },
|
||||
{ title: "Adresse", value: "street" },
|
||||
{ title: "Stadt", value: "city" },
|
||||
{ title: "Versendet", value: "shipped" },
|
||||
{ title: "", value: "edit", width: 130 }
|
||||
]
|
||||
|
||||
orderStore.getAllOrders()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<admin-data-layout
|
||||
:hide-add-button="true"
|
||||
>
|
||||
<v-data-table
|
||||
:headers="headers"
|
||||
:items="orderStore.orders"
|
||||
>
|
||||
<template #item.account="{ item }">
|
||||
{{ item.account.firstName }} {{ item.account.lastName }}
|
||||
</template>
|
||||
|
||||
<template #item.orderedAt="{ item }">
|
||||
{{ moment(item.orderedAt).format("DD.MM.YYYY, HH:mm:ss") }}
|
||||
</template>
|
||||
|
||||
<template #item.street="{ item }">
|
||||
{{ item.address.street }} {{ item.address.houseNumber }}
|
||||
</template>
|
||||
|
||||
<template #item.city="{ item }">
|
||||
{{ item.address.postalCode }} {{ item.address.city }}
|
||||
</template>
|
||||
|
||||
<template #item.shipped="{ item }">
|
||||
<v-icon
|
||||
:icon="item.shipped ? 'mdi-check' : 'mdi-close'"
|
||||
:color="item.shipped ? 'green' : 'red'"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #item.edit="{ item }">
|
||||
<v-btn
|
||||
icon="mdi-eye"
|
||||
variant="plain"
|
||||
@click="orderStore.openDetails(item)"
|
||||
/>
|
||||
|
||||
<v-btn
|
||||
icon="mdi-delete"
|
||||
variant="plain"
|
||||
color="red"
|
||||
@click="orderStore.deleteOrder(item)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
</v-data-table>
|
||||
</admin-data-layout>
|
||||
|
||||
<order-detail-dialog />
|
||||
</template>
|
||||
27
src/pages/admin/ordersAdminPage/orderDetailDialog.vue
Normal file
27
src/pages/admin/ordersAdminPage/orderDetailDialog.vue
Normal file
@@ -0,0 +1,27 @@
|
||||
<script setup lang="ts">
|
||||
import actionDialog from '@/components/basics/actionDialog.vue';
|
||||
import outlinedButton from '@/components/basics/outlinedButton.vue';
|
||||
import { useOrderStore } from '@/stores/order.store';
|
||||
import moment from 'moment';
|
||||
|
||||
const orderStore = useOrderStore()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<action-dialog
|
||||
v-model="orderStore.showDetailDialog"
|
||||
:title="$t('order.order')"
|
||||
icon="mdi-basket"
|
||||
>
|
||||
<v-list>
|
||||
<v-list-subheader>
|
||||
{{ $t('ticket.ticket', 2) }}
|
||||
</v-list-subheader>
|
||||
|
||||
<v-list-item v-for="ticket of orderStore.order.tickets">
|
||||
{{ moment(ticket.concert.date).format("DD.MM.YYYY") }} -
|
||||
{{ ticket.concert.band.name }} - {{ ticket.concert.name }}
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</action-dialog>
|
||||
</template>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user