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 9b325c849e
commit 4bcc2b86d5
49 changed files with 454 additions and 219 deletions

View File

@@ -239,6 +239,60 @@
"bandId": 4 "bandId": 4
} }
] ]
},
{
"id": 5,
"name": "Billy Talent",
"foundingYear": 1993,
"descriptionEn": "Billy Talent is a Canadian rock band from Mississauga, Ontario. They formed in 1993 with lead vocalist Benjamin Kowalewicz, guitarist Ian D'Sa, bassist Jonathan Gallant, and drummer Aaron Solowoniuk. There have been no lineup changes, although Solowoniuk has been on hiatus from the band since 2016 due to a relapse of multiple sclerosis. In the three decades since their inception, Billy Talent has sold well over a million physical albums in Canada alone and nearly 3 million albums internationally. During their most successful period, they were ranked as one of the top 10 best-selling native bands in Canada.",
"descriptionDe": "Billy Talent ist eine kanadische Rockband aus Mississauga, Ontario. Die Band spielte anfangs Punk, ordnet sich auf den späteren Alben jedoch eher im Alternative Rock ein. ",
"images": [ "billy-talent-1.jpg" ],
"logo": "billy-talent-logo.png",
"genreId": [ 2, 11, 12, 13 ],
"members": [
{
"name": "Benjamin Kowalewicz",
"bandId": 5,
"image": "benjamin-kowalewicz.jpg"
},
{
"name": "Ian D'Sa ",
"bandId": 5,
"image": "ian-d-sa.jpg"
},
{
"name": "Jonathan Gallant",
"bandId": 5,
"image": "jonathan-gallant.jpg"
},
{
"name": "Jordan Hastings",
"bandId": 5,
"image": "jordan-hastings.jpg"
},
{
"name": "Josh Freese",
"bandId": 5,
"image": "josh-freese.jpg"
}
],
"ratings": [
{
"accountId": 0,
"rating": 5,
"bandId": 5
},
{
"accountId": 1,
"rating": 3,
"bandId": 5
},
{
"accountId": 2,
"rating": 4,
"bandId": 5
}
]
} }
] ]
} }

View File

@@ -1,39 +0,0 @@
{
"data": [
{
"id": 0,
"icon": "",
"name": "All"
},
{
"id": 1,
"icon": "mdi-chip",
"name": "Electronic"
},
{
"id": 2,
"icon": "mdi-soccer",
"name": "Sports"
},
{
"id": 3,
"icon": "mdi-tshirt-crew",
"name": "Clothes"
},
{
"id": 4,
"icon": "mdi-bookshelf",
"name": "Books"
},
{
"id": 5,
"icon": "mdi-guitar-electric",
"name": "Instruments"
},
{
"id": 6,
"icon": "mdi-teddy-bear",
"name": "Toys"
}
]
}

View File

@@ -43,6 +43,18 @@
{ {
"id": 10, "id": 10,
"name": "Post-Grunge" "name": "Post-Grunge"
},
{
"id": 11,
"name": "Punk-Rock"
},
{
"id": 12,
"name": "Post-Hardcore"
},
{
"id": 13,
"name": "Pop-Punk"
} }
] ]
} }

View File

@@ -2,40 +2,74 @@
"data": [ "data": [
{ {
"id": 0, "id": 0,
"name": "Unlimited Love Tour", "name": "Unlimited Love",
"bandId": 0, "bandId": 0,
"offered": true, "offered": true,
"shows": [ "image": "unlimited-love-tour.jpg",
"concerts": [
{ {
"id": 0, "id": 0,
"date": "2024-12-03", "date": "2024-10-18",
"price": 92, "price": 92,
"inStock": 230, "inStock": 930,
"locationId": 0, "locationId": 0,
"tourId": 0 "tourId": 0
},
{
"id": 1,
"date": "2024-10-23",
"price": 119.90,
"inStock": 8736,
"locationId": 4,
"tourId": 0
},
{
"id": 2,
"date": "2024-10-26",
"price": 114.90,
"inStock": 2793,
"locationId": 8,
"tourId": 0
},
{
"id": 3,
"date": "2024-11-02",
"price": 124.90,
"inStock": 3079,
"locationId": 12,
"tourId": 0
} }
] ]
}, },
{ {
"id": 1, "id": 1,
"name": "The Bends Tour", "name": "The Bends",
"bandId": 1, "bandId": 1,
"offered": true, "offered": true,
"shows": [ "image": "the-bends-tour.jpg",
"concerts": [
{ {
"id": 1, "id": 4,
"date": "2024-11-30", "date": "2024-11-30",
"price": 104, "price": 108,
"inStock": 120, "inStock": 1200,
"locationId": 2, "locationId": 1,
"tourId": 1 "tourId": 1
}, },
{ {
"id": 2, "id": 5,
"date": "2024-12-01", "date": "2024-12-01",
"price": 104, "price": 104,
"inStock": 180, "inStock": 1800,
"locationId": 0, "locationId": 5,
"tourId": 1
},
{
"id": 6,
"date": "2024-12-07",
"price": 99.90,
"inStock": 2438,
"locationId": 9,
"tourId": 1 "tourId": 1
} }
] ]
@@ -45,29 +79,55 @@
"name": "European Tour", "name": "European Tour",
"bandId": 2, "bandId": 2,
"offered": true, "offered": true,
"shows": [ "image": "european-tour-arctic-monkeys.jpg",
"concerts": [
{ {
"id": 3, "id": 7,
"date": "2024-10-15", "date": "2025-01-21",
"price": 80, "price": 67.90,
"inStock": 99, "inStock": 994,
"locationId": 3, "locationId": 3,
"tourId": 2 "tourId": 2
},
{
"id": 8,
"date": "2024-11-15",
"price": 79.90,
"inStock": 1073,
"locationId": 14,
"tourId": 2
} }
] ]
}, },
{ {
"id": 3, "id": 3,
"name": "Moon Music Tour", "name": "Music of the Spheres",
"bandId": 3, "bandId": 3,
"offered": true, "offered": true,
"shows": [ "image": "music-of-the-spheres.png",
"concerts": [
{ {
"id": 4, "id": 9,
"date": "2024-10-15", "date": "2024-12-07",
"price": 80, "price": 124.90,
"inStock": 99, "inStock": 765,
"locationId": 1, "locationId": 13,
"tourId": 3
},
{
"id": 10,
"date": "2025-01-17",
"price": 129.90,
"inStock": 989,
"locationId": 9,
"tourId": 3
},
{
"id": 11,
"date": "2025-02-01",
"price": 134.90,
"inStock": 827,
"locationId": 4,
"tourId": 3 "tourId": 3
} }
] ]
@@ -77,16 +137,42 @@
"name": "But Here We Are Tour", "name": "But Here We Are Tour",
"bandId": 4, "bandId": 4,
"offered": true, "offered": true,
"shows": [ "image": "but-here-we-are.jpg",
"concerts": [
{ {
"id": 5, "id": 12,
"date": "2024-10-15", "date": "2024-12-05",
"price": 80, "price": 80,
"inStock": 99, "inStock": 99,
"locationId": 2, "locationId": 2,
"tourId": 4 "tourId": 4
} }
] ]
},
{
"id": 5,
"name": "Crisis of Faith",
"bandId": 5,
"offered": true,
"image": "crisis-of-faith-tour.jpg",
"concerts": [
{
"id": 13,
"date": "2025-01-12",
"price": 81.90,
"inStock": 173,
"locationId": 2,
"tourId": 5
},
{
"id": 14,
"date": "2025-02-01",
"price": 84.90,
"inStock": 192,
"locationId": 6,
"tourId": 5
}
]
} }
] ]
} }

View File

@@ -11,7 +11,7 @@ import { AccountRole } from "./models/user/accountRole.model"
import { Genre } from "./models/acts/genre.model" import { Genre } from "./models/acts/genre.model"
import { Location } from "./models/acts/location.model" import { Location } from "./models/acts/location.model"
import { Band } from "./models/acts/band.model" import { Band } from "./models/acts/band.model"
import { Show } from "./models/acts/show.model" import { Concert } from "./models/acts/concert.model"
import { Member } from "./models/acts/member.model" import { Member } from "./models/acts/member.model"
import { Rating } from "./models/acts/rating.model" import { Rating } from "./models/acts/rating.model"
import { Tour } from "./models/acts/tour.model" import { Tour } from "./models/acts/tour.model"
@@ -31,7 +31,7 @@ export const sequelize = new Sequelize({
storage: "database.sqlite", storage: "database.sqlite",
models: [ models: [
AccountRole, Account, Payment, Address, AccountRole, Account, Payment, Address,
City, Location, Genre, Band, BandGenre, Rating, Member, Tour, Show, City, Location, Genre, Band, BandGenre, Rating, Member, Tour, Concert,
Order, OrderItem Order, OrderItem
] ]
}) })

Binary file not shown.

After

Width:  |  Height:  |  Size: 395 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 336 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 492 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

View File

@@ -4,7 +4,7 @@ import { Tour } from "./tour.model";
import { OrderItem } from "../ordering/orderItem.model"; import { OrderItem } from "../ordering/orderItem.model";
@Table({ timestamps: false }) @Table({ timestamps: false })
export class Show extends Model { export class Concert extends Model {
@Column @Column
date: String date: String

View File

@@ -1,5 +1,5 @@
import { BelongsTo, Column, ForeignKey, HasMany, Model, Table } from "sequelize-typescript"; import { BelongsTo, Column, ForeignKey, HasMany, Model, Table } from "sequelize-typescript";
import { Show } from "./show.model"; import { Concert } from "./concert.model";
import { City } from "./city.model"; import { City } from "./city.model";
@Table({ timestamps: false }) @Table({ timestamps: false })
@@ -20,8 +20,8 @@ export class Location extends Model {
// Relations // Relations
@HasMany(() => Show) @HasMany(() => Concert)
shows: Show[] shows: Concert[]
@BelongsTo(() => City) @BelongsTo(() => City)
city: City city: City

View File

@@ -1,6 +1,6 @@
import { BelongsTo, Column, ForeignKey, HasMany, Model, Table } from "sequelize-typescript"; import { BelongsTo, Column, ForeignKey, HasMany, Model, Table } from "sequelize-typescript";
import { Band } from "./band.model"; import { Band } from "./band.model";
import { Show } from "./show.model"; import { Concert } from "./concert.model";
@Table({ timestamps: false }) @Table({ timestamps: false })
export class Tour extends Model { export class Tour extends Model {
@@ -13,12 +13,15 @@ export class Tour extends Model {
@Column @Column
offered: Boolean offered: Boolean
@Column
image: String
// Relations // Relations
@BelongsTo(() => Band) @BelongsTo(() => Band)
band: Band band: Band
@HasMany(() => Show) @HasMany(() => Concert)
shows: Show[] shows: Concert[]
} }

View File

@@ -1,5 +1,5 @@
import { Model, BelongsTo, Column, ForeignKey, HasMany, HasOne, Table } from "sequelize-typescript"; import { Model, BelongsTo, Column, ForeignKey, HasMany, HasOne, Table } from "sequelize-typescript";
import { Show } from "../acts/show.model"; import { Concert } from "../acts/concert.model";
import { Order } from "./order.model"; import { Order } from "./order.model";
@Table({ timestamps: false }) @Table({ timestamps: false })
@@ -15,7 +15,7 @@ export class OrderItem extends Model {
orderPrice: number orderPrice: number
@Column @Column
@ForeignKey(() => Show) @ForeignKey(() => Concert)
showId: number showId: number
@@ -23,6 +23,6 @@ export class OrderItem extends Model {
@BelongsTo(() => Order) @BelongsTo(() => Order)
order: Order order: Order
@BelongsTo(() => Show) @BelongsTo(() => Concert)
product: Show product: Concert
} }

View File

@@ -1,13 +1,13 @@
import { Location } from "../models/acts/location.model"; import { Location } from "../models/acts/location.model";
import { Show } from "../models/acts/show.model"; import { Concert } from "../models/acts/concert.model";
import { Request, Response, Router } from "express"; import { Request, Response, Router } from "express";
import { Tour } from "../models/acts/tour.model"; import { Tour } from "../models/acts/tour.model";
import { City } from "../models/acts/city.model"; import { City } from "../models/acts/city.model";
export const show = Router() export const concert = Router()
show.get("/:id", (req: Request, res: Response) => { concert.get("/:id", (req: Request, res: Response) => {
Show.findByPk(req.params.id, { Concert.findByPk(req.params.id, {
include: [ include: [
Tour, Tour,
{ {

View File

@@ -1,6 +1,6 @@
import { Router, Request, Response } from "express"; import { Router, Request, Response } from "express";
import { Order } from "../models/ordering/order.model"; import { Order } from "../models/ordering/order.model";
import { Show } from "../models/acts/show.model"; import { Concert } from "../models/acts/concert.model";
import { OrderItem } from "../models/ordering/orderItem.model"; import { OrderItem } from "../models/ordering/orderItem.model";
import { Payment } from "../models/user/payment.model"; import { Payment } from "../models/user/payment.model";
import { Address } from "../models/user/address.model"; import { Address } from "../models/user/address.model";
@@ -18,7 +18,7 @@ order.get("/:id", (req: Request, res: Response) => {
model: OrderItem, model: OrderItem,
include: [ include: [
{ {
model: Show, model: Concert,
include: [ Band, Location ], include: [ Band, Location ],
attributes: { attributes: {
exclude: [ exclude: [
@@ -50,7 +50,7 @@ order.post("/", (req: Request, res: Response) => {
productId: orderItem.productId productId: orderItem.productId
}) })
Show.decrement( Concert.decrement(
"inStock", "inStock",
{ {
by: orderItem.quantity, by: orderItem.quantity,

View File

@@ -1,4 +1,4 @@
import { Show } from "../models/acts/show.model"; import { Concert } from "../models/acts/concert.model";
import { Band } from "../models/acts/band.model"; import { Band } from "../models/acts/band.model";
import { Tour } from "../models/acts/tour.model"; import { Tour } from "../models/acts/tour.model";
import { Request, Response, Router } from "express"; import { Request, Response, Router } from "express";
@@ -19,7 +19,7 @@ tour.get("/", (req: Request, res: Response) => {
} }
}, },
{ {
model: Show, model: Concert,
include: [ include: [
{ {
model: Location, model: Location,

View File

@@ -9,7 +9,7 @@ import { Member } from '../models/acts/member.model'
import { Genre } from '../models/acts/genre.model' import { Genre } from '../models/acts/genre.model'
import { Band } from '../models/acts/band.model' import { Band } from '../models/acts/band.model'
import { Location } from '../models/acts/location.model' import { Location } from '../models/acts/location.model'
import { Show } from '../models/acts/show.model' import { Concert } from '../models/acts/concert.model'
import { Tour } from '../models/acts/tour.model' import { Tour } from '../models/acts/tour.model'
import { City } from '../models/acts/city.model' import { City } from '../models/acts/city.model'
import { BandGenre } from '../models/acts/bandGenre.model' import { BandGenre } from '../models/acts/bandGenre.model'
@@ -36,7 +36,7 @@ export function deleteAllTables() {
Genre.destroy({ truncate: true }) Genre.destroy({ truncate: true })
Band.destroy({ truncate: true }) Band.destroy({ truncate: true })
Location.destroy({ truncate: true }) Location.destroy({ truncate: true })
Show.destroy({ truncate: true }) Concert.destroy({ truncate: true })
Address.destroy({ truncate: true }) Address.destroy({ truncate: true })
Payment.destroy({ truncate: true }) Payment.destroy({ truncate: true })
@@ -90,8 +90,8 @@ export async function prepopulateDatabase() {
for (let tour of tours.data) { for (let tour of tours.data) {
await Tour.create(tour) await Tour.create(tour)
.then(async dataset => { .then(async dataset => {
for (let show of tour.shows) { for (let concert of tour.concerts) {
await Show.create(show) await Concert.create(concert)
} }
}) })
} }

View File

@@ -5,7 +5,7 @@ import { api } from './routes/api.routes'
import { startDatabase } from './database' import { startDatabase } from './database'
import { order } from './routes/order.routes' import { order } from './routes/order.routes'
import { account } from './routes/account.routes' import { account } from './routes/account.routes'
import { show } from './routes/show.routes' import { concert } from './routes/concert.routes'
import { band } from './routes/band.routes' import { band } from './routes/band.routes'
import { genre } from './routes/genre.routes' import { genre } from './routes/genre.routes'
import { location } from './routes/location.routes' import { location } from './routes/location.routes'
@@ -35,7 +35,7 @@ app.use('/static', express.static(path.join(__dirname, 'images')))
// Routes // Routes
app.use("/api", api) app.use("/api", api)
app.use("/shows", show) app.use("/shows", concert)
app.use("/bands", band) app.use("/bands", band)
app.use("/genres", genre) app.use("/genres", genre)
app.use("/locations", location) app.use("/locations", location)

View File

@@ -6,17 +6,17 @@ import navigationAppendItems from './components/navigation/navigationAppendItems
import navigationPrependItems from './components/navigation/navigationPrependItems.vue'; import navigationPrependItems from './components/navigation/navigationPrependItems.vue';
import { usePreferencesStore } from './data/stores/preferencesStore'; import { usePreferencesStore } from './data/stores/preferencesStore';
import { useFeedbackStore } from './data/stores/feedbackStore'; import { useFeedbackStore } from './data/stores/feedbackStore';
import { useShowStore } from './data/stores/showStore'; import { useConcertStore } from './data/stores/concertStore';
const preferencesStore = usePreferencesStore() const preferencesStore = usePreferencesStore()
const showStore = useShowStore() const concertStore = useConcertStore()
const feedbackStore = useFeedbackStore() const feedbackStore = useFeedbackStore()
const theme = useTheme() const theme = useTheme()
theme.global.name.value = preferencesStore.theme theme.global.name.value = preferencesStore.theme
showStore.fetchAllTours() concertStore.fetchAllTours()
// Global watcher // Global watcher
watch(() => preferencesStore.language, () => { watch(() => preferencesStore.language, () => {
@@ -64,7 +64,7 @@ watch(() => preferencesStore.language, () => {
</v-snackbar> </v-snackbar>
<!-- Here changes the router the content --> <!-- 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"> <v-sheet color="sheet">
<router-view></router-view> <router-view></router-view>
</v-sheet> </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"> <script setup lang="ts">
defineProps({ defineProps({
image: String, image: String,
title: String title: String,
smallerTitle: {
type: Boolean,
default: false
}
}) })
</script> </script>
@@ -17,9 +21,16 @@ defineProps({
cover cover
/> />
<v-card-title v-if="title"> <div v-if="title">
{{ title }} <v-card-title v-if="!smallerTitle">
</v-card-title> {{ 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"> <div class="px-4 pb-4" v-if="$slots.default">
<slot></slot> <slot></slot>

View File

@@ -10,12 +10,12 @@
<v-divider vertical /> <v-divider vertical />
<v-btn <v-btn
to="/shows/events" to="/shows/concerts"
prepend-icon="mdi-ticket" prepend-icon="mdi-ticket"
height="100%" height="100%"
:rounded="false" :rounded="false"
> >
{{ $t('menu.allEvents', 2) }} {{ $t('menu.allConcerts', 2) }}
</v-btn> </v-btn>
<v-divider vertical /> <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 logo: string
ratings: Array<RatingModel> ratings: Array<RatingModel>
members: Array<MemberModel> members: Array<MemberModel>
genre: { genres: {
name: string name: string
} }
} }

View File

@@ -1,12 +1,12 @@
import { ShowModel } from "./showModel" import { ConcertModel } from "./concertModel"
export class BasketItemModel { export class BasketItemModel {
id: number = -1 id: number = -1
quantity: 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.quantity = quantity
this.product = product this.concert = concert
} }
} }

View File

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

View File

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

View File

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

View File

@@ -1,10 +1,11 @@
import { BandModel } from "./bandModel" import { BandModel } from "./bandModel"
import { ShowModel } from "./showModel" import { ConcertModel } from "./concertModel"
export class TourModel { export class TourModel {
id: number id: number
name: string name: string
offered: boolean offered: boolean
band: BandModel 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 { BannerStateEnum } from "../enums/bannerStateEnum";
import { addOrder } from "../api/orderApi"; import { addOrder } from "../api/orderApi";
import { useAccountStore } from "./accountStore"; import { useAccountStore } from "./accountStore";
import { ShowModel } from "../models/showModel"; import { ConcertModel } from "../models/concertModel";
import { AddressModel } from "../models/addressModel"; import { AddressModel } from "../models/addressModel";
import { PaymentModel } from "../models/paymentModel"; import { PaymentModel } from "../models/paymentModel";
@@ -44,28 +44,28 @@ export const useBasketStore = defineStore('basketStore', {
feedbackStore.changeBanner(BannerStateEnum.BASKETPRODUCTREMOVED) feedbackStore.changeBanner(BannerStateEnum.BASKETPRODUCTREMOVED)
this.itemsInBasket = this.itemsInBasket.filter((basketItemModel: BasketItemModel) => 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 * 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 * @param quantity Quantity of the product
*/ */
addItemToBasket(show: ShowModel, quantity: number) { addItemToBasket(concert: ConcertModel, quantity: number) {
const feedbackStore = useFeedbackStore() const feedbackStore = useFeedbackStore()
feedbackStore.changeBanner(BannerStateEnum.BASKETPRODUCTADDED) feedbackStore.changeBanner(BannerStateEnum.BASKETPRODUCTADDED)
// Product is already in the basket, increase number of items // Product is already in the basket, increase number of items
if (this.itemsInBasket.find((basketItem: BasketItemModel) => if (this.itemsInBasket.find((basketItem: BasketItemModel) =>
basketItem.product.id == show.id)) basketItem.concert.id == concert.id))
{ {
this.itemsInBasket.find((basketItem: BasketItemModel) => this.itemsInBasket.find((basketItem: BasketItemModel) =>
basketItem.product.id == show.id).quantity += quantity basketItem.concert.id == concert.id).quantity += quantity
} else { } 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 { CityModel } from "../models/cityModel";
import { getAllCities } from "../api/cityApi"; import { getAllCities } from "../api/cityApi";
export const useShowStore = defineStore("showStore", { export const useConcertStore = defineStore("concertStore", {
state: () => ({ state: () => ({
tours: useLocalStorage<Array<TourModel>>("hackmycart/showStore/tours", []), tours: useLocalStorage<Array<TourModel>>("hackmycart/concertStore/tours", []),
bands: useLocalStorage<Array<BandModel>>("hackmycart/showStore/bands", []), bands: useLocalStorage<Array<BandModel>>("hackmycart/concertStore/bands", []),
locations: useLocalStorage<Array<LocationModel>>("hackmycart/showStore/locations", []), locations: useLocalStorage<Array<LocationModel>>("hackmycart/concertStore/locations", []),
cities: useLocalStorage<Array<CityModel>>("hackmycart/showStore/cities", []), cities: useLocalStorage<Array<CityModel>>("hackmycart/concertStore/cities", []),
genres: useLocalStorage<Array<GenreModel>>("hackmycart/showStore/genres", []) genres: useLocalStorage<Array<GenreModel>>("hackmycart/concertStore/genres", [])
}), }),
actions: { actions: {
@@ -25,6 +25,10 @@ export const useShowStore = defineStore("showStore", {
await getAllTours() await getAllTours()
.then(result => { .then(result => {
this.tours = result.data 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() await getAllBands()

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,8 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import OutlinedButton from '@/components/outlinedButton.vue'; 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> </script>
<template> <template>
@@ -29,7 +29,7 @@ const tourStore = useShowStore()
</template> </template>
<v-carousel-item <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]" :src="'http://localhost:3000/static/bands/' + tour.band.images[0]"
cover cover
> >

View File

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

View File

@@ -1,15 +1,15 @@
<script setup lang="ts"> <script setup lang="ts">
import sectionDivider from '@/components/sectionDivider.vue'; 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 cardWithTopImage from '@/components/cardWithTopImage.vue';
import { calcRating } from '@/scripts/showsScripts'; import { calcRating } from '@/scripts/concertScripts';
const showStore = useShowStore() const concertStore = useConcertStore()
</script> </script>
<template> <template>
<v-container> <v-container>
<div v-for="genre in showStore.genres"> <div v-for="genre in concertStore.genres">
<v-row> <v-row>
<v-col> <v-col>
<section-divider <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"> <script setup lang="ts">
import sectionDivider from '@/components/sectionDivider.vue'; 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 cardWithTopImage from '@/components/cardWithTopImage.vue';
const showStore = useShowStore() const concertStore = useConcertStore()
</script> </script>
<template> <template>
<v-container> <v-container>
<div v-for="city in showStore.cities"> <div v-for="city in concertStore.cities">
<v-row> <v-row>
<v-col> <v-col>
<section-divider <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 BandsPage from "@/pages/shows/bandsPage/index.vue";
import LocationsPage from "@/pages/shows/locationsPage/index.vue" import LocationsPage from "@/pages/shows/locationsPage/index.vue"
import SearchPage from "@/pages/shows/searchPage/index.vue" import SearchPage from "@/pages/shows/searchPage/index.vue"
export const showRoutes = [ export const showRoutes = [
{ path: '/shows/events', component: EventsPage }, { path: '/shows/concerts', component: ConcertsPage },
{ path: '/shows/bands', component: BandsPage }, { path: '/shows/bands', component: BandsPage },
{ path: '/shows/locations', component: LocationsPage }, { path: '/shows/locations', component: LocationsPage },
{ path: '/shows/search', component: SearchPage }, { 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
}