Remove EventTable in database, redesign frontend URL paths

This commit is contained in:
2024-10-12 15:54:03 +02:00
parent 1d4daac9ae
commit 203f8428a7
40 changed files with 955 additions and 1203 deletions

View File

@@ -0,0 +1,60 @@
<script setup lang="ts">
import { useBasketStore } from '@/data/stores/basketStore';
import cardView from '@/components/basics/cardView.vue';
import orderingDialog from './orderingDialog.vue';
import outlinedButton from '@/components/basics/outlinedButton.vue';
import { ref } from 'vue';
import { useAccountStore } from '@/data/stores/accountStore';
import ticketsTable from './ticketsTable.vue';
const basketStore = useBasketStore()
const accountStore = useAccountStore()
const showOrderingDialog = ref()
</script>
<template>
<v-container max-width="1000">
<v-row>
<v-col>
<card-view
:title="$t('basket')"
:subtitle="basketStore.itemsInBasket.length + ' ' +
$tc('product.product', basketStore.itemsInBasket.length)"
v-model="showOrderingDialog"
icon="mdi-cart"
>
<!-- Display items if basket is not empty -->
<div v-if="basketStore.itemsInBasket.length > 0">
<tickets-table />
</div>
<!-- Display empty state if card is empty -->
<v-empty-state v-else
icon="mdi-basket-off"
:title="$t('emptyBasketTitle')"
:text="$t('emptyBasketText')"
/>
<v-card-text class="text-right text-h5" v-if="basketStore.itemsInBasket.length > 0">
{{ $t('totalPrice') }}: {{ (basketStore.getTotalPrice).toFixed(2) }}
</v-card-text>
<template #actions>
<outlined-button
prepend-icon="mdi-basket-check"
:disabled="basketStore.itemsInBasket.length == 0 || accountStore.userAccount.id == null"
variant="outlined"
color="green"
@click="showOrderingDialog = true"
>
{{ $t('orderNow') }}
</outlined-button>
</template>
</card-view>
</v-col>
</v-row>
</v-container>
<ordering-dialog v-model="showOrderingDialog" />
</template>

View File

@@ -0,0 +1,109 @@
<script setup lang="ts">
import actionDialog from '@/components/basics/actionDialog.vue';
import { useBasketStore } from '@/data/stores/basketStore';
import outlinedButton from '@/components/basics/outlinedButton.vue';
import { ModelRef, ref } from 'vue';
import { useAccountStore } from '@/data/stores/accountStore';
const basketStore = useBasketStore()
const accountStore = useAccountStore()
const showDialog: ModelRef<boolean> = defineModel()
const orderingInProgress = ref(false)
const addressError = ref(false)
const paymentError = ref(false)
async function doOrder() {
orderingInProgress.value = true
addressError.value = false
paymentError.value = false
if (basketStore.usedAddress == null) {
addressError.value = true
}
if (basketStore.usedPayment == null){
paymentError.value = true
}
if (basketStore.usedAddress != null && basketStore.usedPayment != null) {
await basketStore.takeOrder()
showDialog.value = false
}
orderingInProgress.value = false
}
</script>
<template>
<action-dialog
:title="$t('ordering.ordering')"
icon="mdi-basket-check"
v-model="showDialog"
max-width="800"
persistent
>
<v-row>
<v-col>
{{ $t('account.address', accountStore.userAccount.addresses.length) }}
</v-col>
</v-row>
<v-row>
<v-col>
<v-radio-group
v-model="basketStore.usedAddress"
:error="addressError"
>
<v-radio
v-for="address in accountStore.userAccount.addresses"
:value="address"
:label="address.street + ' ' + address.houseNumber + ', ' + address.postalCode + ' ' + address.city"
/>
</v-radio-group>
</v-col>
</v-row>
<v-row>
<v-col>
{{ $t('account.payment', accountStore.userAccount.payments.length) }}
</v-col>
</v-row>
<v-row>
<v-col>
<v-radio-group
v-model="basketStore.usedPayment"
>
<v-radio
v-for="payment in accountStore.userAccount.payments"
:value="payment"
:label="payment.bankName + ': ' + payment.iban"
:error="paymentError"
/>
</v-radio-group>
</v-col>
</v-row>
<template #actions>
<outlined-button
@click="showDialog = false"
prepend-icon="mdi-close"
color="orange"
:disabled="orderingInProgress"
>
{{ $t('dialog.cancel') }}
</outlined-button>
<outlined-button
@click="doOrder"
:loading="orderingInProgress"
prepend-icon="mdi-send"
color="green"
>
{{ $t('ordering.takeOrder') }}
</outlined-button>
</template>
</action-dialog>
</template>

View File

@@ -0,0 +1,67 @@
<script setup lang="ts">
import { useBasketStore } from '@/data/stores/basketStore';
import { BasketItemModel } from '@/data/models/ordering/basketItemModel';
import { calcPrice } from '@/scripts/concertScripts';
const basketStore = useBasketStore()
function removeFromBasket(basketItem: BasketItemModel) {
basketStore.removeItemFromBasket(basketItem)
}
</script>
<template>
<v-table>
<thead>
<tr>
<th>{{ $t('band') }}</th>
<th>{{ $t('event') }}</th>
<th class="text-center">{{ $t('quantity') }}</th>
<th class="text-right">{{ $t('product.productPrice') }}</th>
<th class="text-right">{{ $t('totalPrice') }}</th>
<th></th>
</tr>
</thead>
<tbody>
<tr v-for="basketItem in basketStore.itemsInBasket">
<!-- Band name -->
<td>
{{ basketItem.band.name }}
</td>
<!-- Event name -->
<td>
{{ basketItem.event.name }}
</td>
<!-- Quantity -->
<td class="text-center">
{{ basketItem.seats.length }}x
</td>
<!-- Price per event -->
<td class="text-right">
<div v-if="basketItem.seats">
{{ basketItem.price }}
</div>
</td>
<!-- Total price -->
<td class="text-right">
{{ (calcPrice(basketItem.concert.price, basketItem.seats.length)).toFixed(2) }}
</td>
<td>
<v-btn
icon="mdi-delete"
@click="removeFromBasket(basketItem)"
color="red"
variant="text"
flat
/>
</td>
</tr>
</tbody>
</v-table>
</template>

View File

@@ -0,0 +1,37 @@
<script setup lang="ts">
import { getAllExerciseGroups } from '@/data/api/exerciseApi';
import scoreCard from './scoreCard.vue';
import { ref } from 'vue';
import { ExerciseGroupModel } from '@/data/models/exercises/exerciseGroupModel';
import { useFeedbackStore } from '@/data/stores/feedbackStore';
const exerciseGroups = ref<Array<ExerciseGroupModel>>([])
const feedbackStore = useFeedbackStore()
feedbackStore.fetchDataFromServerInProgress = true
getAllExerciseGroups()
.then(result => {
exerciseGroups.value = result.data
feedbackStore.fetchDataFromServerInProgress = false
})
</script>
<template>
<v-container max-width="1000">
<v-row v-if="feedbackStore.fetchDataFromServerInProgress" v-for="i in 3">
<v-col>
<score-card :loading="true"
/>
</v-col>
</v-row>
<v-row v-for="exerciseGroup in exerciseGroups">
<v-col>
<score-card
:exercise-group="exerciseGroup"
/>
</v-col>
</v-row>
</v-container>
</template>

View File

@@ -0,0 +1,86 @@
<script setup lang="ts">
import cardView from '@/components/basics/cardView.vue';
import { ExerciseGroupModel } from '@/data/models/exercises/exerciseGroupModel';
defineProps({
exerciseGroup: ExerciseGroupModel,
loading: Boolean
})
</script>
<template>
<card-view v-if="loading" :loading="loading" >
<v-timeline
direction="horizontal"
side="start"
class="pb-3"
>
<v-timeline-item
v-for="i in 3"
dot-color="grey"
icon="mdi-pencil"
>
<v-skeleton-loader
type="list-item"
:loading="loading"
width="200"
/>
<template #opposite>
<v-skeleton-loader
type="sentences"
:loading="loading"
width="200"
/>
</template>
</v-timeline-item>
</v-timeline>
</card-view>
<card-view
v-else
:title="$t('exerciseGroup') + ' ' + exerciseGroup.groupNr + ': ' + exerciseGroup.nameDe"
:loading="loading"
>
<v-timeline
direction="horizontal"
side="start"
class="pb-3"
>
<v-timeline-item
v-for="exercise in exerciseGroup.exercises"
:dot-color="exercise.solved ? 'green' : 'grey'"
:icon="exercise.solved ? 'mdi-check' : 'mdi-pencil'"
>
<v-skeleton-loader
type="text"
:loading="loading"
>
<div class="text-h6">
{{ $t('exercise') }} {{ exercise.exerciseNr }}
</div>
</v-skeleton-loader>
<template #opposite>
<v-skeleton-loader
type="text"
:loading="loading"
>
<div class="text-center">
<div class="text-h6">
{{ exercise.nameDe }}
</div>
<div>
{{ exercise.descriptionDe }}
</div>
</div>
</v-skeleton-loader>
</template>
</v-timeline-item>
</v-timeline>
</card-view>
</template>

View File

@@ -0,0 +1,20 @@
<script setup lang="ts">
import pageSetup from './pageSetup.vue';
import systemSetup from './systemSetup.vue';
</script>
<template>
<v-container max-width="800">
<v-row>
<v-col>
<page-setup />
</v-col>
</v-row>
<v-row>
<v-col>
<system-setup />
</v-col>
</v-row>
</v-container>
</template>

View File

@@ -0,0 +1,50 @@
<script setup lang="ts">
import { ThemeEnum } from '@/data/enums/themeEnums';
import { useTheme } from 'vuetify/lib/framework.mjs';
import { i18n } from '@/plugins/i18n';
import cardView from '@/components/basics/cardView.vue';
import { usePreferencesStore } from '@/data/stores/preferencesStore';
const preferencesStore = usePreferencesStore()
const theme = useTheme()
const themeEnums = Object.values(ThemeEnum)
function changeTheme() {
theme.global.name.value = preferencesStore.theme
}
function changeLanguage() {
i18n.global.locale = preferencesStore.language
}
</script>
<template>
<card-view
:title="$t('preferences.pageSetup')"
icon="mdi-view-dashboard"
>
<v-row>
<v-col>
<v-select
v-model="preferencesStore.theme"
:items="themeEnums"
:label="$t('preferences.selectedTheme')"
@update:model-value="changeTheme"
hide-details
/>
</v-col>
</v-row>
<v-row>
<v-col>
<v-select
v-model="preferencesStore.language"
:items="$i18n.availableLocales"
:label="$t('preferences.language')"
@update:model-value="changeLanguage"
hide-details
/>
</v-col>
</v-row>
</card-view>
</template>

View File

@@ -0,0 +1,132 @@
<script setup lang="ts">
import { BannerStateEnum } from '@/data/enums/bannerStateEnum';
import { useFeedbackStore } from '@/data/stores/feedbackStore';
import cardView from '@/components/basics/cardView.vue';
import outlinedButton from '@/components/basics/outlinedButton.vue';
import { ref } from 'vue';
import confirmDialog from '@/components/basics/confirmDialog.vue';
import { getServerState, resetDatabase, resetExerciseProgress } from '@/data/api/mainApi';
import { ServerStateEnum } from '@/data/enums/serverStateEnum';
import packageJson from './../../../../package.json'
const feedbackStore = useFeedbackStore()
const showConfirmDeleteDbDialog = ref(false)
const showConfirmDeleteExerciseProgressDialog = ref(false)
const serverOnline = ref(ServerStateEnum.PENDING)
getServerState()
.then(result => {
if (result.status == 200) {
serverOnline.value = ServerStateEnum.ONLINE
} else {
serverOnline.value = ServerStateEnum.OFFLINE
}
})
.catch(error => {
serverOnline.value = ServerStateEnum.OFFLINE
})
async function resetDb() {
serverOnline.value = ServerStateEnum.PENDING
await resetDatabase()
.then(result => {
if (result.status == 200) {
feedbackStore.changeBanner(BannerStateEnum.DATABASERESETSUCCESSFUL)
serverOnline.value = ServerStateEnum.ONLINE
}
showConfirmDeleteDbDialog.value = false
})
}
async function resetExerciseProg() {
serverOnline.value = ServerStateEnum.PENDING
await resetExerciseProgress()
.then(result => {
if (result.status == 200) {
feedbackStore.changeBanner(BannerStateEnum.EXERCISEPROGRESSRESETSUCCESSFUL)
serverOnline.value = ServerStateEnum.ONLINE
}
showConfirmDeleteExerciseProgressDialog.value = false
})
}
</script>
<template>
<card-view
:title="$t('preferences.systemSetup')"
icon="mdi-engine"
>
<v-row>
<v-col>
{{ $t('serverState') }}:
<span v-if="serverOnline == ServerStateEnum.ONLINE" class="text-green">
<v-icon icon="mdi-check" />
Online
</span>
<span v-else-if="serverOnline == ServerStateEnum.OFFLINE" class="text-red">
<v-icon icon="mdi-alert-circle" />
Offline
</span>
<span v-else-if="serverOnline == ServerStateEnum.PENDING" class="text-orange">
<v-icon icon="mdi-clock" />
Pending...
</span>
</v-col>
</v-row>
<v-row>
<v-col>
Software Version: {{ packageJson.version }}
</v-col>
</v-row>
<v-row>
<v-col class="d-flex justify-center align-center">
<outlined-button
@click="showConfirmDeleteDbDialog = true"
prepend-icon="mdi-database-refresh"
color="red"
:disabled="serverOnline != ServerStateEnum.ONLINE"
>
{{ $t('resetDatabase') }}
</outlined-button>
</v-col>
</v-row>
<v-row>
<v-col class="d-flex justify-center align-center">
<outlined-button
@click="showConfirmDeleteExerciseProgressDialog = true"
prepend-icon="mdi-progress-close"
color="red"
:disabled="serverOnline != ServerStateEnum.ONLINE"
>
{{ $t('resetProgress') }}
</outlined-button>
</v-col>
</v-row>
</card-view>
<!-- Confirm delete database -->
<confirm-dialog
:title="$t('resetDatabaseConfirm.title')"
:description="$t('resetDatabaseConfirm.description')"
v-model="showConfirmDeleteDbDialog"
:onConfirm="resetDb"
/>
<!-- Confirm delete exercise progress -->
<confirm-dialog
:title="$t('resetExerciseProgressConfirm.title')"
:description="$t('resetExerciseProgressConfirm.description')"
v-model="showConfirmDeleteExerciseProgressDialog"
:onConfirm="resetExerciseProg"
/>
</template>

View File

@@ -0,0 +1,151 @@
<script setup lang="ts">
import searchBar from './searchBar.vue';
import eventListItem from '@/components/pageParts/eventListItem.vue';
import sectionDivider from '@/components/basics/sectionDivider.vue';
import cardViewHorizontal from '@/components/basics/cardViewHorizontal.vue';
import locationListItem from '@/components/pageParts/locationListItem.vue';
import cardViewTopImage from '@/components/basics/cardViewTopImage.vue';
import bandListItem from '@/components/pageParts/bandListItem.vue';
import { useSearchStore } from '@/data/stores/searchStore';
const searchStore = useSearchStore()
</script>
<template>
<v-container>
<v-row>
<v-spacer />
<v-col cols="10">
<v-row>
<v-col>
<search-bar />
</v-col>
</v-row>
<div v-if="searchStore.alreadySearched">
<v-row>
<v-col>
<section-divider :title="$t('band', 2)" />
</v-col>
</v-row>
<v-row
v-if="searchStore.searchInProgress"
v-for="i in 2"
>
<v-col>
<card-view-horizontal :loading="true" />
</v-col>
</v-row>
<v-row
v-else-if="searchStore.bands.length > 0"
v-for="band in searchStore.bands">
<v-col>
<band-list-item
:band="band"
:events="band.events"
:genres="band.genres"
:loading="searchStore.searchInProgress"
/>
</v-col>
</v-row>
<v-row v-else >
<v-col>
<v-empty-state
:title="$t('noBandFound')"
icon="mdi-guitar-electric"
/>
</v-col>
</v-row>
<!-- Section Location results -->
<v-row>
<v-col>
<section-divider :title="$t('location', 2)" />
</v-col>
</v-row>
<v-row
v-if="searchStore.searchInProgress"
>
<v-col v-for="i in 4">
<card-view-top-image :loading="true" />
</v-col>
</v-row>
<v-row
v-else-if="searchStore.locations.length > 0"
>
<v-col
cols="3"
v-for="locaiton in searchStore.locations"
>
<location-list-item
:location="locaiton"
:concerts="locaiton.concerts"
/>
</v-col>
</v-row>
<v-row v-else >
<v-col>
<v-empty-state
:title="$t('noLocationsFound')"
icon="mdi-city"
/>
</v-col>
</v-row>
<!-- Section Event results -->
<v-row>
<v-col>
<section-divider :title="$t('event', 2)" />
</v-col>
</v-row>
<v-row
v-if="searchStore.searchInProgress"
v-for="i in 2"
>
<v-col>
<card-view-horizontal :loading="true" />
</v-col>
</v-row>
<v-row
v-else-if="searchStore.events.length > 0"
v-for="event in searchStore.events"
>
<v-col>
<event-list-item
:event="event"
:band="event.band"
:concerts="event.concerts"
:loading="searchStore.searchInProgress"
/>
</v-col>
</v-row>
<v-row v-else >
<v-col>
<v-empty-state
:title="$t('noEventsFound')"
icon="mdi-party-popper"
/>
</v-col>
</v-row>
</div>
</v-col>
<v-spacer />
</v-row>
</v-container>
</template>

View File

@@ -0,0 +1,26 @@
<script setup lang="ts">
import cardView from '@/components/basics/cardView.vue';
import { useSearchStore } from '@/data/stores/searchStore';
const searchStore = useSearchStore()
</script>
<template>
<card-view >
<v-text-field
variant="outlined"
hide-details
v-model="searchStore.searchTerm"
:placeholder="$t('enterSomeKeywords')"
@keyup.enter="searchStore.startSearch"
>
<template #append-inner>
<v-btn
icon="mdi-magnify"
variant="plain"
@click="searchStore.startSearch"
/>
</template>
</v-text-field>
</card-view>
</template>