Display concerts with card views on "All concerts" page, adding image property for tours

This commit is contained in:
2024-09-28 21:18:25 +02:00
parent 8d0b141217
commit 0616409f14
49 changed files with 454 additions and 219 deletions

View File

@@ -6,17 +6,17 @@ import navigationAppendItems from './components/navigation/navigationAppendItems
import navigationPrependItems from './components/navigation/navigationPrependItems.vue';
import { usePreferencesStore } from './data/stores/preferencesStore';
import { useFeedbackStore } from './data/stores/feedbackStore';
import { useShowStore } from './data/stores/showStore';
import { useConcertStore } from './data/stores/concertStore';
const preferencesStore = usePreferencesStore()
const showStore = useShowStore()
const concertStore = useConcertStore()
const feedbackStore = useFeedbackStore()
const theme = useTheme()
theme.global.name.value = preferencesStore.theme
showStore.fetchAllTours()
concertStore.fetchAllTours()
// Global watcher
watch(() => preferencesStore.language, () => {
@@ -64,7 +64,7 @@ watch(() => preferencesStore.language, () => {
</v-snackbar>
<!-- Here changes the router the content -->
<v-container max-width="1200" class="pt-0 pb-5">
<v-container max-width="1400" class="pt-0 pb-5">
<v-sheet color="sheet">
<router-view></router-view>
</v-sheet>

View File

@@ -0,0 +1,33 @@
<script setup lang="ts">
defineProps({
image: String,
title: String
})
</script>
<template>
<v-card
variant="tonal"
link
>
<v-row>
<v-col cols="auto" class="pr-0">
<v-img :src="image" aspect-ratio="1" width="140" cover />
</v-col>
<v-col class="pl-0">
<v-card-title v-if="title">
{{ title }}
</v-card-title>
<div class="px-4 pb-4" v-if="$slots.default">
<slot></slot>
</div>
</v-col>
<v-col cols="2" height="100%">
<slot name="append"></slot>
</v-col>
</v-row>
</v-card>
</template>

View File

@@ -1,7 +1,11 @@
<script setup lang="ts">
defineProps({
image: String,
title: String
title: String,
smallerTitle: {
type: Boolean,
default: false
}
})
</script>
@@ -17,9 +21,16 @@ defineProps({
cover
/>
<v-card-title v-if="title">
{{ title }}
</v-card-title>
<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>
<div class="px-4 pb-4" v-if="$slots.default">
<slot></slot>

View File

@@ -10,12 +10,12 @@
<v-divider vertical />
<v-btn
to="/shows/events"
to="/shows/concerts"
prepend-icon="mdi-ticket"
height="100%"
:rounded="false"
>
{{ $t('menu.allEvents', 2) }}
{{ $t('menu.allConcerts', 2) }}
</v-btn>
<v-divider vertical />

View File

@@ -0,0 +1,7 @@
import axios from "axios"
let BASE_URL = "http://localhost:3000/concerts"
export async function getAllConcerts() {
return await axios.get(BASE_URL)
}

View File

@@ -1,7 +0,0 @@
import axios from "axios"
let BASE_URL = "http://localhost:3000/shows"
export async function getAllShows() {
return await axios.get(BASE_URL)
}

View File

@@ -12,7 +12,7 @@ export class BandModel {
logo: string
ratings: Array<RatingModel>
members: Array<MemberModel>
genre: {
genres: {
name: string
}
}

View File

@@ -1,12 +1,12 @@
import { ShowModel } from "./showModel"
import { ConcertModel } from "./concertModel"
export class BasketItemModel {
id: number = -1
quantity: number = 1
product: ShowModel = new ShowModel()
concert: ConcertModel = new ConcertModel()
constructor(quantity: number, product: ShowModel) {
constructor(quantity: number, concert: ConcertModel) {
this.quantity = quantity
this.product = product
this.concert = concert
}
}

View File

@@ -1,6 +1,6 @@
import { LocationModel } from "./locationModel"
export class ShowModel {
export class ConcertModel {
id: number
inStock: number
date: string

View File

@@ -1,8 +1,8 @@
import { ShowModel } from "./showModel"
import { ConcertModel } from "./concertModel"
export class OrderItemModel {
orderId: number = -1
quantity: number = 1
orderPrice: number = 0
product: ShowModel
product: ConcertModel
}

View File

@@ -1,4 +1,3 @@
import { AccountModel } from "./accountModel"
import { BandModel } from "./bandModel"
export class RatingModel {

View File

@@ -1,10 +1,11 @@
import { BandModel } from "./bandModel"
import { ShowModel } from "./showModel"
import { ConcertModel } from "./concertModel"
export class TourModel {
id: number
name: string
offered: boolean
band: BandModel
shows: Array<ShowModel>
image: string
shows: Array<ConcertModel>
}

View File

@@ -5,7 +5,7 @@ import { useFeedbackStore } from "./feedbackStore";
import { BannerStateEnum } from "../enums/bannerStateEnum";
import { addOrder } from "../api/orderApi";
import { useAccountStore } from "./accountStore";
import { ShowModel } from "../models/showModel";
import { ConcertModel } from "../models/concertModel";
import { AddressModel } from "../models/addressModel";
import { PaymentModel } from "../models/paymentModel";
@@ -44,28 +44,28 @@ export const useBasketStore = defineStore('basketStore', {
feedbackStore.changeBanner(BannerStateEnum.BASKETPRODUCTREMOVED)
this.itemsInBasket = this.itemsInBasket.filter((basketItemModel: BasketItemModel) =>
basketItemModel.product.id != item.product.id
basketItemModel.concert.id != item.concert.id
)
},
/**
* Add an item to the basket. If the product is already in the basket, the quantity will increase
*
* @param show Show to add
* @param concert Concert to add
* @param quantity Quantity of the product
*/
addItemToBasket(show: ShowModel, quantity: number) {
addItemToBasket(concert: ConcertModel, quantity: number) {
const feedbackStore = useFeedbackStore()
feedbackStore.changeBanner(BannerStateEnum.BASKETPRODUCTADDED)
// Product is already in the basket, increase number of items
if (this.itemsInBasket.find((basketItem: BasketItemModel) =>
basketItem.product.id == show.id))
basketItem.concert.id == concert.id))
{
this.itemsInBasket.find((basketItem: BasketItemModel) =>
basketItem.product.id == show.id).quantity += quantity
basketItem.concert.id == concert.id).quantity += quantity
} else {
this.itemsInBasket.push(new BasketItemModel(quantity, show))
this.itemsInBasket.push(new BasketItemModel(quantity, concert))
}
},

View File

@@ -11,13 +11,13 @@ import { getAllGenres } from "../api/genreApi";
import { CityModel } from "../models/cityModel";
import { getAllCities } from "../api/cityApi";
export const useShowStore = defineStore("showStore", {
export const useConcertStore = defineStore("concertStore", {
state: () => ({
tours: useLocalStorage<Array<TourModel>>("hackmycart/showStore/tours", []),
bands: useLocalStorage<Array<BandModel>>("hackmycart/showStore/bands", []),
locations: useLocalStorage<Array<LocationModel>>("hackmycart/showStore/locations", []),
cities: useLocalStorage<Array<CityModel>>("hackmycart/showStore/cities", []),
genres: useLocalStorage<Array<GenreModel>>("hackmycart/showStore/genres", [])
tours: useLocalStorage<Array<TourModel>>("hackmycart/concertStore/tours", []),
bands: useLocalStorage<Array<BandModel>>("hackmycart/concertStore/bands", []),
locations: useLocalStorage<Array<LocationModel>>("hackmycart/concertStore/locations", []),
cities: useLocalStorage<Array<CityModel>>("hackmycart/concertStore/cities", []),
genres: useLocalStorage<Array<GenreModel>>("hackmycart/concertStore/genres", [])
}),
actions: {
@@ -25,6 +25,10 @@ export const useShowStore = defineStore("showStore", {
await getAllTours()
.then(result => {
this.tours = result.data
this.tours.sort((a, b) => {
return new Date(a.shows[0].date) < new Date(b.shows[0].date) ? -1 : 1
})
})
await getAllBands()

View File

@@ -5,7 +5,7 @@ import { LanguageEnum } from "../enums/languageEnum";
export const usePreferencesStore = defineStore('preferencesStore', {
state: () => ({
theme: useLocalStorage<ThemeEnum>("hackmycart/preferencesStore/theme", ThemeEnum.DARKRED),
theme: useLocalStorage<ThemeEnum>("hackmycart/preferencesStore/theme", ThemeEnum.DARKBLUE),
language: useLocalStorage<LanguageEnum>("hackmycart/preferencesStore/language", LanguageEnum.GERMAN)
}),
})

View File

@@ -1,12 +1,13 @@
{
"menu": {
"allEvents": "Alle Events",
"allConcerts": "Alle Konzerte",
"allLocations": "Alle Veranstaltungsorte",
"allBands": "Alle Bands"
},
"shows": {
"highlights": "Highlights",
"tickets": "Ticket | Tickets",
"topEvents": "Top Events",
"topConcerts": "Top Konzerte",
"topBands": "Top Bands",
"topLocations": "Top Veranstaltungsorte"
},

View File

@@ -1,12 +1,13 @@
{
"menu": {
"allEvents": "All Events",
"allConcerts": "All Concerts",
"allLocations": "All Locations",
"allBands": "All Bands"
},
"shows": {
"highlights": "Highlights",
"tickets": "Ticket | Tickets",
"topEvents": "Top Events",
"topConcerts": "Top Concerts",
"topBands": "Top Bands",
"topLocations": "Top Locations"
},

View File

@@ -30,18 +30,18 @@ function editQuantity(basketItem: BasketItemModel) {
<tbody>
<tr v-for="basketItem in basketStore.itemsInBasket">
<!-- Category icon and name -->
<td><v-icon :icon="basketItem.product.category.icon" />
{{ basketItem.product.category.name }}
<td><v-icon :icon="basketItem.concert.category.icon" />
{{ basketItem.concert.category.name }}
</td>
<!-- Product brand -->
<td>
{{ basketItem.product.brand.name }}
{{ basketItem.concert.brand.name }}
</td>
<!-- Name of product -->
<td>
{{ basketItem.product.name }}
{{ basketItem.concert.name }}
</td>
<!-- Quantity -->
@@ -73,18 +73,18 @@ function editQuantity(basketItem: BasketItemModel) {
<!-- Total price -->
<td class="text-right">
<div v-if="basketItem.product.discount > 0">
<div v-if="basketItem.concert.discount > 0">
<strong class="font-weight-bold text-body-1 text-red-lighten-1">
{{ calcPrice(basketItem.product.price, basketItem.product.discount, basketItem.quantity) }}
{{ calcPrice(basketItem.concert.price, basketItem.concert.discount, basketItem.quantity) }}
</strong>
<div class="text-decoration-line-through ml-3 mt-1 text-caption">
{{ calcPrice(basketItem.product.price, 0, basketItem.quantity) }}
{{ calcPrice(basketItem.concert.price, 0, basketItem.quantity) }}
</div>
</div>
<div v-else>
{{ calcPrice(basketItem.product.price, 0, basketItem.quantity) }}
{{ calcPrice(basketItem.concert.price, 0, basketItem.quantity) }}
</div>
</td>

View File

@@ -1,8 +1,8 @@
<script setup lang="ts">
import OutlinedButton from '@/components/outlinedButton.vue';
import { useShowStore } from '@/data/stores/showStore';
import { useConcertStore } from '@/data/stores/concertStore';
const tourStore = useShowStore()
const concertStore = useConcertStore()
</script>
<template>
@@ -29,7 +29,7 @@ const tourStore = useShowStore()
</template>
<v-carousel-item
v-for="tour in tourStore.tours"
v-for="tour in concertStore.tours"
:src="'http://localhost:3000/static/bands/' + tour.band.images[0]"
cover
>

View File

@@ -1,13 +1,13 @@
<script setup lang="ts">
import { useShowStore } from '@/data/stores/showStore';
import { useConcertStore } from '@/data/stores/concertStore';
import highlightCarousel from './highlightCarousel.vue';
import sectionDivider from '@/components/sectionDivider.vue';
import cardWithTopImage from '@/components/cardWithTopImage.vue';
import { calcRating } from '@/scripts/showsScripts';
import { calcRating, lowestTicketPrice } from '@/scripts/concertScripts';
import OutlinedButton from '@/components/outlinedButton.vue';
import router from '@/plugins/router';
const showStore = useShowStore()
const concertStore = useConcertStore()
</script>
<template>
@@ -16,17 +16,18 @@ const showStore = useShowStore()
<v-container>
<v-row>
<v-col>
<section-divider :title="$t('shows.topEvents')" />
<section-divider :title="$t('shows.highlights')" />
</v-col>
</v-row>
<v-row>
<v-col v-for="i in 4" cols="3">
<v-col v-for="i in 6" cols="2">
<card-with-top-image
:image="'bands/' + showStore.tours[i].band.images[0]"
:title="showStore.tours[i].name"
:image="'tours/' + concertStore.tours[i - 1].image"
:title="concertStore.tours[i - 1].band.name"
smaller-title
>
{{ showStore.bands[i].name }}
Tickets ab {{ lowestTicketPrice(concertStore.tours[i - 1]) }}
</card-with-top-image>
</v-col>
</v-row>
@@ -35,10 +36,10 @@ const showStore = useShowStore()
<v-col>
<outlined-button
append-icon="mdi-chevron-right"
@click="router.push('/shows/events')"
@click="router.push('/shows/concerts')"
block
>
{{ $t('menu.allEvents') }}
{{ $t('menu.allConcerts') }}
</outlined-button>
</v-col>
</v-row>
@@ -53,10 +54,10 @@ const showStore = useShowStore()
<v-row>
<v-col v-for="i in 4" cols="3">
<card-with-top-image
:image="'bands/' + showStore.bands[i - 1].logo"
:title="showStore.bands[i - 1].name"
:image="'bands/' + concertStore.bands[i - 1].logo"
:title="concertStore.bands[i - 1].name"
>
{{ showStore.bands[i - 1].genre.name }}
{{ concertStore.bands[i - 1].genres.name }}
<div class="d-flex justify-center pt-3">
<v-rating
@@ -65,7 +66,7 @@ const showStore = useShowStore()
size="large"
half-increments
active-color="orange"
:model-value="calcRating(showStore.bands[i - 1].ratings)"
:model-value="calcRating(concertStore.bands[i - 1].ratings)"
/>
</div>
</card-with-top-image>
@@ -92,13 +93,13 @@ const showStore = useShowStore()
</v-row>
<v-row>
<v-col v-for="i in 4" cols="3">
<v-col v-for="i in 6" cols="2">
<card-with-top-image
:image="'locations/' + showStore.locations[i + 2].image"
:title="showStore.locations[i + 2].name"
:image="'locations/' + concertStore.locations[i + 2].image"
:title="concertStore.locations[i + 2].name"
smaller-title
>
<div>{{ showStore.locations[i + 2].address }}</div>
{{ showStore.locations[i + 2].city.name }}, {{ showStore.locations[i + 2].city.country }}
{{ concertStore.locations[i + 2].city.name }}, {{ concertStore.locations[i + 2].city.country }}
</card-with-top-image>
</v-col>
</v-row>
@@ -116,5 +117,3 @@ const showStore = useShowStore()
</v-row>
</v-container>
</template>

View File

@@ -1,15 +1,15 @@
<script setup lang="ts">
import sectionDivider from '@/components/sectionDivider.vue';
import { useShowStore } from '@/data/stores/showStore';
import { useConcertStore } from '@/data/stores/concertStore';
import cardWithTopImage from '@/components/cardWithTopImage.vue';
import { calcRating } from '@/scripts/showsScripts';
import { calcRating } from '@/scripts/concertScripts';
const showStore = useShowStore()
const concertStore = useConcertStore()
</script>
<template>
<v-container>
<div v-for="genre in showStore.genres">
<div v-for="genre in concertStore.genres">
<v-row>
<v-col>
<section-divider

View File

@@ -0,0 +1,37 @@
<script setup lang="ts">
import cardWithLeftImage from '@/components/cardWithLeftImage.vue';
import { useConcertStore } from '@/data/stores/concertStore';
import { createDateRangeString, lowestTicketPrice } from '@/scripts/concertScripts';
const concertStore = useConcertStore()
</script>
<template>
<v-container>
<v-row>
<v-spacer />
<v-col cols="10">
<v-row v-for="tour of concertStore.tours">
<v-col>
<card-with-left-image
:title="tour.band.name + ' - ' + tour.name"
:image="'http://localhost:3000/static/tours/' + tour.image"
>
{{ createDateRangeString(tour) }}
<div>{{ tour.shows.length }} {{ $t('tours.concert', tour.shows.length) }}</div>
<template #append>
<div class="d-flex justify-center align-center text-h5" style="height: 100%;">
ab {{ lowestTicketPrice(tour) }}
</div>
</template>
</card-with-left-image>
</v-col>
</v-row>
</v-col>
<v-spacer />
</v-row>
</v-container>
</template>

View File

@@ -1,6 +0,0 @@
<script setup lang="ts">
</script>
<template>
Events
</template>

View File

@@ -1,14 +1,14 @@
<script setup lang="ts">
import sectionDivider from '@/components/sectionDivider.vue';
import { useShowStore } from '@/data/stores/showStore';
import { useConcertStore } from '@/data/stores/concertStore';
import cardWithTopImage from '@/components/cardWithTopImage.vue';
const showStore = useShowStore()
const concertStore = useConcertStore()
</script>
<template>
<v-container>
<div v-for="city in showStore.cities">
<div v-for="city in concertStore.cities">
<v-row>
<v-col>
<section-divider

View File

@@ -1,10 +1,10 @@
import EventsPage from "@/pages/shows/eventsPage/index.vue";
import ConcertsPage from "@/pages/shows/concertsPage/index.vue";
import BandsPage from "@/pages/shows/bandsPage/index.vue";
import LocationsPage from "@/pages/shows/locationsPage/index.vue"
import SearchPage from "@/pages/shows/searchPage/index.vue"
export const showRoutes = [
{ path: '/shows/events', component: EventsPage },
{ path: '/shows/concerts', component: ConcertsPage },
{ path: '/shows/bands', component: BandsPage },
{ path: '/shows/locations', component: LocationsPage },
{ path: '/shows/search', component: SearchPage },

View File

@@ -0,0 +1,65 @@
import { RatingModel } from "@/data/models/ratingModel"
import { dateToHumanReadableString } from "./dateTimeScripts"
import { TourModel } from "@/data/models/tourModel"
/**
* Calculate a price based on parameters
*
* @param price Original price for one unit
* @param discount Discount in percent
* @param quantity Number of units
*
* @returns Price rounded to two digits
*/
export function calcPrice(price: number, discount: number = 0, quantity: number = 1): number {
return Math.round(quantity * price * ((100 - discount) / 100) * 100) / 100
}
/**
* Calculate the average of an Array of ratings
*
* @param ratings Array of ratings
*
* @returns Average rating as number
*/
export function calcRating(ratings: Array<RatingModel>) {
let sum = 0
for (let rating of ratings) {
sum += rating.rating
}
return sum / ratings.length
}
export function createDateRangeString(tour: TourModel) {
const dateArray = []
for (let concert of tour.shows) {
dateArray.push(new Date(concert.date))
}
dateArray.sort(function (a, b) {
return a - b
})
if (dateArray.length > 1) {
return dateToHumanReadableString(dateArray[0]) + ' - ' +
dateToHumanReadableString(dateArray[dateArray.length - 1])
} else {
return dateToHumanReadableString(dateArray[0])
}
}
export function lowestTicketPrice(tour: TourModel): string {
const priceArray : Array<number> = []
for (let concert of tour.shows) {
priceArray.push(concert.price)
}
priceArray.sort()
return priceArray[0].toFixed(2)
}

View File

@@ -0,0 +1,5 @@
export function dateToHumanReadableString(date: Date) {
return String(date.getDate()).padStart(2, '0') + '.' +
String(date.getMonth() + 1).padStart(2, '0') + '.' +
date.getFullYear()
}

View File

@@ -1,31 +0,0 @@
import { RatingModel } from "@/data/models/ratingModel"
/**
* Calculate a price based on parameters
*
* @param price Original price for one unit
* @param discount Discount in percent
* @param quantity Number of units
*
* @returns Price rounded to two digits
*/
export function calcPrice(price: number, discount: number = 0, quantity: number = 1): number {
return Math.round(quantity * price * ((100 - discount) / 100) * 100) / 100
}
/**
* Calculate the average of an Array of ratings
*
* @param ratings Array of ratings
*
* @returns Average rating as number
*/
export function calcRating(ratings: Array<RatingModel>) {
let sum = 0
for (let rating of ratings) {
sum += rating.rating
}
return sum / ratings.length
}