Refactor frontend, display tours with cards on ToursPage

This commit is contained in:
2024-09-26 16:06:20 +02:00
parent f5204578e4
commit 941fd711d5
39 changed files with 397 additions and 349 deletions

View File

@@ -1,6 +1,6 @@
<mxfile host="Electron" agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/24.7.8 Chrome/128.0.6613.36 Electron/32.0.1 Safari/537.36" version="24.7.8"> <mxfile host="Electron" agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/24.7.8 Chrome/128.0.6613.36 Electron/32.0.1 Safari/537.36" version="24.7.8">
<diagram name="Page-1" id="WevClHWmhzPAQ7FDN5po"> <diagram name="Page-1" id="WevClHWmhzPAQ7FDN5po">
<mxGraphModel dx="3113" dy="447" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1169" pageHeight="827" math="0" shadow="0"> <mxGraphModel dx="3263" dy="534" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1169" pageHeight="827" math="0" shadow="0">
<root> <root>
<mxCell id="0" /> <mxCell id="0" />
<mxCell id="1" parent="0" /> <mxCell id="1" parent="0" />
@@ -572,7 +572,7 @@
</mxCell> </mxCell>
<mxCell id="EQeajuEG8KHzwlrw_xps-164" value="0..n" style="resizable=0;align=left;verticalAlign=top;labelBackgroundColor=#ffffff;fontSize=10;strokeColor=#003366;shadow=1;fillColor=#D4E1F5;fontColor=#003366" parent="EQeajuEG8KHzwlrw_xps-163" connectable="0" vertex="1"> <mxCell id="EQeajuEG8KHzwlrw_xps-164" value="0..n" style="resizable=0;align=left;verticalAlign=top;labelBackgroundColor=#ffffff;fontSize=10;strokeColor=#003366;shadow=1;fillColor=#D4E1F5;fontColor=#003366" parent="EQeajuEG8KHzwlrw_xps-163" connectable="0" vertex="1">
<mxGeometry x="-1" relative="1" as="geometry"> <mxGeometry x="-1" relative="1" as="geometry">
<mxPoint x="-22" y="-28" as="offset" /> <mxPoint x="3" y="-29" as="offset" />
</mxGeometry> </mxGeometry>
</mxCell> </mxCell>
<mxCell id="EQeajuEG8KHzwlrw_xps-165" value="1" style="resizable=0;align=right;verticalAlign=top;labelBackgroundColor=#ffffff;fontSize=10;strokeColor=#003366;shadow=1;fillColor=#D4E1F5;fontColor=#003366" parent="EQeajuEG8KHzwlrw_xps-163" connectable="0" vertex="1"> <mxCell id="EQeajuEG8KHzwlrw_xps-165" value="1" style="resizable=0;align=right;verticalAlign=top;labelBackgroundColor=#ffffff;fontSize=10;strokeColor=#003366;shadow=1;fillColor=#D4E1F5;fontColor=#003366" parent="EQeajuEG8KHzwlrw_xps-163" connectable="0" vertex="1">

View File

@@ -38,6 +38,50 @@
"bandId": 0 "bandId": 0
} }
] ]
},
{
"id": 1,
"name": "Radiohead",
"foundingYear": 1985,
"descriptionEn": "Radiohead are an English rock band formed in Abingdon, Oxfordshire, in 1985. They comprise Thom Yorke (vocals, guitar, piano, keyboards); brothers Jonny Greenwood (guitar, keyboards, other instruments) and Colin Greenwood (bass); Ed O'Brien (guitar, backing vocals); and Philip Selway (drums, percussion). They have worked with the producer Nigel Godrich and the cover artist Stanley Donwood since 1994. Radiohead's experimental approach is credited with advancing the sound of alternative rock.",
"descriptionDe": "Radiohead ist eine britische Rockband, die 1985 in Oxford, England gegründet wurde. Die Band besteht aus Thom Yorke (Gesang, Rhythmusgitarre, Piano), Jonny Greenwood (Lead-Gitarre, Keyboard, Ondes Martenot), Colin Greenwood (E-Bass, Keyboard), Ed OBrien (Gitarre, Backgroundvocals) und Phil Selway (Schlagzeug, Backgroundvocals). Radioheads experimenteller Ansatz gilt als Wegbereiter für den Sound des Alternative Rocks.",
"images": [ "radiohead.jpg" ],
"logo": "radiohead-logo.png",
"genreId": 1,
"members": [
{
"name": "Thom Yorke",
"bandId": 1,
"image": "thom-yorke.jpg"
},
{
"name": "Jonny Greenwood",
"bandId": 1,
"image": "jonny-greenwood.jpg"
},
{
"name": "Colin Greenwood",
"bandId": 1,
"image": "colin-greenwood.jpg"
},
{
"name": "Ed O'Brien",
"bandId": 1,
"image": "ed-o-brien.jpg"
},
{
"name": "Philip Selway",
"bandId": 1,
"image": "philip-selway.jpg"
}
],
"ratings": [
{
"accountId": 0,
"rating": 5,
"bandId": 1
}
]
} }
] ]
} }

View File

@@ -3,6 +3,10 @@
{ {
"id": 0, "id": 0,
"name": "Funk rock" "name": "Funk rock"
},
{
"id": 1,
"name": "Art rock"
} }
] ]
} }

View File

@@ -15,6 +15,30 @@
"tourId": 0 "tourId": 0
} }
] ]
},
{
"id": 1,
"name": "The Bends Tour",
"bandId": 1,
"offered": true,
"shows": [
{
"id": 1,
"date": "2024-11-30",
"price": 104,
"inStock": 120,
"locationId": 0,
"tourId": 1
},
{
"id": 2,
"date": "2024-12-01",
"price": 104,
"inStock": 180,
"locationId": 0,
"tourId": 1
}
]
} }
] ]
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

View File

@@ -4,21 +4,19 @@ import { i18n } from './plugins/i18n';
import { ref, watch } from 'vue'; import { ref, watch } from 'vue';
import vuetify from './plugins/vuetify'; import vuetify from './plugins/vuetify';
import navigationItems from './components/navigationItems.vue'; import navigationItems from './components/navigationItems.vue';
import { useProductStore } from './data/stores/productStore';
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 { useTourStore } from './data/stores/tourStore';
const preferencesStore = usePreferencesStore() const preferencesStore = usePreferencesStore()
const productStore = useProductStore() const tourStore = useTourStore()
const feedbackStore = useFeedbackStore() const feedbackStore = useFeedbackStore()
const theme = useTheme() const theme = useTheme()
const navRail = ref(vuetify.display.mobile) const navRail = ref(vuetify.display.mobile)
theme.global.name.value = preferencesStore.theme theme.global.name.value = preferencesStore.theme
productStore.fetchAllProducts() tourStore.fetchAllTours()
productStore.fetchAllCategories()
productStore.fetchAllBrands()
// Global watcher // Global watcher
watch(() => preferencesStore.language, () => { watch(() => preferencesStore.language, () => {

View File

@@ -7,28 +7,72 @@ defineProps({
}, },
subtitle: { subtitle: {
type: String, type: String,
},
prependImage: {
type: String,
default: ""
} }
}) })
</script> </script>
<template> <template>
<v-card <v-card>
:title="title" <v-row v-if="prependImage != ''">
:subtitle="subtitle" <v-col cols="3" class="pr-0">
:prepend-icon="icon" <v-img
:src="prependImage"
height="220"
cover
> >
<!-- Show default container only, if there is content --> <template #placeholder>
<v-container v-if="$slots.default"> <v-skeleton-loader
type="image"
height="300"
cover
/>
</template>
</v-img>
</v-col>
<v-col class="pl-0" >
<v-card-title>
{{ title }}
</v-card-title>
<v-card-subtitle>
{{ subtitle }}
</v-card-subtitle>
<div class="pa-4">
<slot></slot> <slot></slot>
</v-container> </div>
<!-- Slot for content without padding --> <v-card-actions v-if="$slots.actions" class="card-actions position-absolute bottom-0 right-0">
<slot name="withoutContainer"></slot>
<!-- Slot for Action Buttons in the right bottom corner -->
<v-card-actions v-if="$slots.actions" class="card-actions">
<v-spacer /> <v-spacer />
<slot name="actions"></slot> <slot name="actions"></slot>
</v-card-actions> </v-card-actions>
</v-col>
</v-row>
<v-container v-else>
<slot></slot>
</v-container>
<!-- Show default container only, if there is content -->
<!-- <v-container v-if="$slots.default">
<slot></slot>
</v-container> -->
<!-- Slot for content without padding -->
<!-- <slot name="withoutContainer"></slot> -->
<!-- Slot for Action Buttons in the right bottom corner -->
<!-- <v-card-actions v-if="$slots.actions" class="card-actions">
<v-spacer />
<slot name="actions"></slot>
</v-card-actions> -->
</v-card> </v-card>
</template> </template>

View File

@@ -12,12 +12,12 @@ const navRail = defineModel("navRail", { type: Boolean })
<!-- Shopping Section --> <!-- Shopping Section -->
<v-list-subheader> <v-list-subheader>
<div v-if="!navRail">{{ $t('menu.shopping') }}</div> <div v-if="!navRail">{{ $t('menu.shopping.shopping') }}</div>
<div v-else></div> <div v-else></div>
</v-list-subheader> </v-list-subheader>
<v-list-item :title="$t('menu.products')" prepend-icon="mdi-store" to="/" link /> <v-list-item :title="$t('menu.shopping.ticket', 2)" prepend-icon="mdi-ticket" to="/" link />
<v-list-item :title="$t('menu.basket')" to="/basket" link > <v-list-item :title="$t('menu.shopping.basket')" to="/basket" link >
<template v-slot:prepend> <template v-slot:prepend>
<v-badge color="primary" :content="basketStore.itemsInBasket.length"> <v-badge color="primary" :content="basketStore.itemsInBasket.length">
<v-icon icon="mdi-cart" /> <v-icon icon="mdi-cart" />
@@ -31,21 +31,21 @@ const navRail = defineModel("navRail", { type: Boolean })
<!-- Account Section --> <!-- Account Section -->
<v-list-subheader> <v-list-subheader>
<div v-if="!navRail">{{ $t('menu.account') }}</div> <div v-if="!navRail">{{ $t('menu.account.accountManagement') }}</div>
<div v-else></div> <div v-else></div>
</v-list-subheader> </v-list-subheader>
<v-expand-transition> <v-expand-transition>
<div v-if="accountStore.userAccount.id == null"> <div v-if="accountStore.userAccount.id == null">
<v-list-item v-if="accountStore.userAccount.id == null" :title="$t('menu.login')" prepend-icon="mdi-login" to="/login" link /> <v-list-item v-if="accountStore.userAccount.id == null" :title="$t('menu.account.login')" prepend-icon="mdi-login" to="/login" link />
</div> </div>
</v-expand-transition> </v-expand-transition>
<v-expand-transition> <v-expand-transition>
<div v-if="accountStore.userAccount.id != null"> <div v-if="accountStore.userAccount.id != null">
<v-list-item :title="$t('menu.account')" prepend-icon="mdi-account" to="/account" link /> <v-list-item :title="$t('menu.account.account')" prepend-icon="mdi-account" to="/account" link />
<v-list-item :title="$t('menu.orders')" prepend-icon="mdi-cart-check" to="/orders" link /> <v-list-item :title="$t('menu.account.order', 2)" prepend-icon="mdi-cart-check" to="/orders" link />
<v-list-item :title="$t('menu.logout')" prepend-icon="mdi-logout" @click="accountStore.logout" link /> <v-list-item :title="$t('menu.account.logout')" prepend-icon="mdi-logout" @click="accountStore.logout" link />
</div> </div>
</v-expand-transition> </v-expand-transition>
@@ -55,12 +55,12 @@ const navRail = defineModel("navRail", { type: Boolean })
<!-- System and help section --> <!-- System and help section -->
<v-list-subheader> <v-list-subheader>
<div v-if="!navRail">{{ $t('menu.systemAndHelp') }}</div> <div v-if="!navRail">{{ $t('menu.systemAndHelp.systemAndHelp') }}</div>
<div v-else></div> <div v-else></div>
</v-list-subheader> </v-list-subheader>
<v-list-item :title="$t('menu.helpInstructions')" prepend-icon="mdi-chat-question" to="/help" link /> <v-list-item :title="$t('menu.systemAndHelp.helpInstructions')" prepend-icon="mdi-chat-question" to="/help" link />
<v-list-item :title="$t('menu.scoreBoard')" prepend-icon="mdi-podium-gold" to="/scoreBoard" link /> <v-list-item :title="$t('menu.systemAndHelp.scoreBoard')" prepend-icon="mdi-podium-gold" to="/scoreBoard" link />
<v-list-item :title="$t('menu.preferences')" prepend-icon="mdi-cog" to="/preferences" link /> <v-list-item :title="$t('menu.systemAndHelp.preferences')" prepend-icon="mdi-cog" to="/preferences" link />
<div v-if="accountStore.userAccount.accountRole.privilegeAdminPanel"> <div v-if="accountStore.userAccount.accountRole.privilegeAdminPanel">
@@ -72,10 +72,6 @@ const navRail = defineModel("navRail", { type: Boolean })
</v-list-subheader> </v-list-subheader>
<v-list-item :title="$t('menu.admin.dashboard')" prepend-icon="mdi-view-dashboard" to="/admin/dashboard" link /> <v-list-item :title="$t('menu.admin.dashboard')" prepend-icon="mdi-view-dashboard" to="/admin/dashboard" link />
<v-list-item :title="$t('menu.admin.categories')" prepend-icon="mdi-label" to="/admin/categories" link />
<v-list-item :title="$t('brand', 2)" prepend-icon="mdi-factory" to="/admin/brands" link />
<v-list-item :title="$t('menu.admin.products')" prepend-icon="mdi-store-cog" to="/admin/products" link />
<v-list-item :title="$t('menu.admin.accounts')" prepend-icon="mdi-account-multiple" to="/admin/accounts" link />
</div> </div>
</v-list> </v-list>

View File

@@ -0,0 +1,11 @@
import axios from "axios"
let BASE_URL = "http://localhost:3000/bands"
export async function getAllBands() {
return await axios.get(BASE_URL)
}
export async function getOneBand(id: number) {
return await axios.get(BASE_URL + '/' + id)
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,10 +0,0 @@
import axios from "axios"
let BASE_URL = "http://localhost:3000/products"
/**
* Fetch all products from API
*/
export async function getAllProducts() {
return await axios.get(BASE_URL)
}

View File

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

View File

@@ -0,0 +1,10 @@
import axios from "axios"
let BASE_URL = "http://localhost:3000/tours"
/**
* Fetch all tours from API
*/
export async function getAllTours() {
return await axios.get(BASE_URL)
}

View File

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

View File

@@ -0,0 +1,5 @@
export class CityModel {
id: Number
name: String
country: String
}

View File

@@ -1,7 +1,9 @@
import { CityModel } from "./cityModel"
export class LocationModel { export class LocationModel {
id: Number id: Number
name: String name: String
address: String address: String
city: String city: CityModel
image: String image: String
} }

View File

@@ -3,6 +3,5 @@ import { BandModel } from "./bandModel"
export class MemberModel { export class MemberModel {
id: Number id: Number
name: String name: String
band: BandModel
image: String image: String
} }

View File

@@ -3,7 +3,6 @@ import { BandModel } from "./bandModel"
export class RatingModel { export class RatingModel {
id: Number id: Number
account: AccountModel
rating: Number rating: Number
band: BandModel band: BandModel
} }

View File

@@ -2,9 +2,9 @@ import { BandModel } from "./bandModel"
import { ShowModel } from "./showModel" import { ShowModel } from "./showModel"
export class TourModel { export class TourModel {
id: Number id: number
name: String name: string
offered: boolean
band: BandModel band: BandModel
offered: Boolean
shows: Array<ShowModel> shows: Array<ShowModel>
} }

View File

@@ -6,8 +6,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 { ProductModel } from "../models/productModel"; import { ShowModel } from "../models/showModel";
import { useProductStore } from "./productStore";
import { AddressModel } from "../models/addressModel"; import { AddressModel } from "../models/addressModel";
import { PaymentModel } from "../models/paymentModel"; import { PaymentModel } from "../models/paymentModel";
@@ -53,21 +52,21 @@ export const useBasketStore = defineStore('basketStore', {
/** /**
* 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 product Product to add * @param show Show to add
* @param quantity Quantity of the product * @param quantity Quantity of the product
*/ */
addItemToBasket(product: ProductModel, quantity: number) { addItemToBasket(show: ShowModel, 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 == product.id)) basketItem.product.id == show.id))
{ {
this.itemsInBasket.find((basketItem: BasketItemModel) => this.itemsInBasket.find((basketItem: BasketItemModel) =>
basketItem.product.id == product.id).quantity += quantity basketItem.product.id == show.id).quantity += quantity
} else { } else {
this.itemsInBasket.push(new BasketItemModel(quantity, product)) this.itemsInBasket.push(new BasketItemModel(quantity, show))
} }
}, },
@@ -75,22 +74,23 @@ export const useBasketStore = defineStore('basketStore', {
* Take an order to the server. Sends all articles in the basket and creates an order entry in the backend database * Take an order to the server. Sends all articles in the basket and creates an order entry in the backend database
*/ */
async takeOrder() { async takeOrder() {
const accountStore = useAccountStore() // todo
const productStore = useProductStore() // const accountStore = useAccountStore()
const feedbackStore = useFeedbackStore() // const productStore = useProductStore()
// const feedbackStore = useFeedbackStore()
await addOrder(accountStore.userAccount.id, this.itemsInBasket, this.usedPayment.id, this.usedAddress.id) // await addOrder(accountStore.userAccount.id, this.itemsInBasket, this.usedPayment.id, this.usedAddress.id)
.then(async result => { // .then(async result => {
if (result.status == 201) { // if (result.status == 201) {
await accountStore.refreshOrders() // await accountStore.refreshOrders()
await productStore.fetchAllProducts() // await productStore.fetchAllProducts()
this.itemsInBasket = [] // this.itemsInBasket = []
feedbackStore.changeBanner(BannerStateEnum.ORDERPLACESUCCESSFUL) // feedbackStore.changeBanner(BannerStateEnum.ORDERPLACESUCCESSFUL)
} else { // } else {
feedbackStore.changeBanner(BannerStateEnum.ERROR) // feedbackStore.changeBanner(BannerStateEnum.ERROR)
} // }
}) // })
} }
} }
}) })

View File

@@ -1,92 +0,0 @@
import { useLocalStorage } from "@vueuse/core";
import { defineStore } from "pinia";
import { getAllProducts } from "../api/productApi";
import { SortOrder } from "../enums/sortOrderEnum";
import { CategoryModel } from "../models/categoryModel";
import { ProductModel } from "../models/productModel";
import { BrandModel } from "../models/brandModel";
import { getAllCategories } from "../api/categoryApi";
import { getAllBrands } from "../api/brandApi";
export const useProductStore = defineStore("productStore", {
state: () => ({
products: useLocalStorage<Array<ProductModel>>("hackmycart/productStore/products", []),
filteredProducts: useLocalStorage<Array<ProductModel>>("hackmycart/productStore/filteredProducts", []),
sortOrder: useLocalStorage<SortOrder>("hackmycart/productStore/sortOrder", SortOrder.NAMEATOZ),
filteredCategory: useLocalStorage<CategoryModel>("hackmycart/productStore/filteredCategory", new CategoryModel()),
onlyDiscounts: useLocalStorage<Boolean>("hackmycart/productStore/onlyDiscounts", false),
brands: useLocalStorage<Array<BrandModel>>("hackmycart/productStore/brands", []),
categories: useLocalStorage<Array<CategoryModel>>("hackmycart/productStore/categories", [])
}),
actions: {
async fetchAllProducts() {
await getAllProducts()
.then(products => {
this.products = products.data
this.filteredProducts = products.data
})
},
async fetchAllCategories() {
await getAllCategories()
.then(categories => {
this.categories = categories.data
})
},
async fetchAllBrands() {
await getAllBrands()
.then(brands => {
this.brands = brands.data
})
},
async filterProducts() {
if (this.filteredCategory.id == -1 || this.filteredCategory.id == 0) {
this.filteredProducts = this.products
} else {
this.filteredProducts = this.products.filter((product: ProductModel) =>
product.category.id == this.filteredCategory.id
)
}
if (this.onlyDiscounts) {
this.filteredProducts = this.filteredProducts.filter((product: ProductModel) =>
product.discount > 0
)
}
},
sortProducts() {
this.filteredProducts.sort((a: ProductModel, b: ProductModel) => {
switch (this.sortOrder)
{
case SortOrder.PRICELOWTOHIGH: {
return a.price - b.price
}
case SortOrder.PRICEHIGHTOLOW: {
return b.price - a.price
}
case SortOrder.NAMEATOZ: {
if (b.name > a.name) {
return -1
}
else {
return 1
}
}
case SortOrder.NAMEZTOA: {
if (b.name < a.name) {
return -1
}
else {
return 1
}
}
}
})
}
}
})

View File

@@ -0,0 +1,21 @@
import { useLocalStorage } from "@vueuse/core";
import { defineStore } from "pinia";
import { TourModel } from "../models/tourModel";
import { getAllTours } from "../api/tourApi";
import { GenreModel } from "../models/genreModel";
export const useTourStore = defineStore("tourStore", {
state: () => ({
tours: useLocalStorage<Array<TourModel>>("hackmycart/tourStore/tours", []),
genres: useLocalStorage<Array<GenreModel>>("hackmycart/tourStore/genres", [])
}),
actions: {
async fetchAllTours() {
await getAllTours()
.then(result => {
this.tours = result.data
})
}
}
})

View File

@@ -1,24 +1,32 @@
{ {
"menu": { "menu": {
"shopping": {
"shopping": "Einkaufen", "shopping": "Einkaufen",
"products": "Produkte", "ticket": "Ticket | Tickets",
"basket": "Warenkorb", "basket": "Warenkorb"
"login": "Login", },
"account": {
"accountManagement": "Kontoverwaltung",
"account": "Account", "account": "Account",
"orders": "Bestellungen", "login": "Login",
"order": "Bestellung | Bestellungen",
"logout": "Ausloggen"
},
"systemAndHelp": {
"systemAndHelp": "System & Hilfe", "systemAndHelp": "System & Hilfe",
"helpInstructions": "Hilfestellung", "helpInstructions": "Hilfestellung",
"preferences": "Einstellungen", "preferences": "Einstellungen",
"logout": "Ausloggen", "scoreBoard": "Score Board"
"scoreBoard": "Score Board", },
"admin": { "admin": {
"admin": "Administration", "admin": "Administration",
"dashboard": "Dashboard", "dashboard": "Dashboard"
"categories": "Kategorien",
"products": "Produkte",
"accounts": "Accounts"
} }
}, },
"tours": {
"concert": "Konzert | Konzerte"
},
"preferences": { "preferences": {
"pageSetup": "Seiteneinstellungen", "pageSetup": "Seiteneinstellungen",
"selectedTheme": "Ausgewähltes Theme", "selectedTheme": "Ausgewähltes Theme",

View File

@@ -1,24 +1,33 @@
{ {
"menu": { "menu": {
"shopping": {
"shopping": "Shopping", "shopping": "Shopping",
"products": "Products", "ticket": "Ticket | Tickets",
"basket": "Basket", "basket": "Basket"
},
"account": {
"account": "Account Management",
"login": "Login", "login": "Login",
"account": "Account", "order": "Order | Orders",
"orders": "Orders",
"systemAndHelp": "System & Help",
"helpInstructions": "Help instructions",
"preferences": "Preferences",
"logout": "Logout", "logout": "Logout",
"scoreBoard": "Score Board", "accountManagement": "Account Management"
},
"systemAndHelp": {
"systemAndHelp": "System & Help",
"helpInstructions": "Help Instructions",
"preferences": "Preferences",
"scoreBoard": "Score Board"
},
"admin": { "admin": {
"admin": "Administration", "admin": "Administration",
"dashboard": "Dashboard", "dashboard": "Dashboard"
"categories": "Categories",
"products": "Products",
"accounts": "Accounts"
} }
}, },
"tours": {
"concert": "Concert | Concerts"
},
"preferences": { "preferences": {
"pageSetup": "Page setup", "pageSetup": "Page setup",
"selectedTheme": "Selected theme", "selectedTheme": "Selected theme",

View File

@@ -1,8 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import cardView from '@/components/cardView.vue'; import cardView from '@/components/cardView.vue';
import { useProductStore } from '@/data/stores/productStore'; // import { useProductStore } from '@/data/stores/productStore';
const productStore = useProductStore() // const productStore = useProductStore()
const headers = [ const headers = [
{ title: "Name", value: "name" }, { title: "Name", value: "name" },
@@ -11,7 +11,7 @@ const headers = [
</script> </script>
<template> <template>
<v-container max-width="800"> <!-- <v-container max-width="800">
<v-row> <v-row>
<v-col> <v-col>
<card-view <card-view
@@ -24,9 +24,8 @@ const headers = [
:headers="headers" :headers="headers"
> >
</v-data-table> </v-data-table>
<!-- todo: Edit/Delete -->
</card-view> </card-view>
</v-col> </v-col>
</v-row> </v-row>
</v-container> </v-container> -->
</template> </template>

View File

@@ -1,8 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import cardView from '@/components/cardView.vue'; import cardView from '@/components/cardView.vue';
import { useProductStore } from '@/data/stores/productStore';
const productStore = useProductStore()
const headers = [ const headers = [
{ title: "Name", value: "name" }, { title: "Name", value: "name" },
@@ -12,24 +9,5 @@ const headers = [
</script> </script>
<template> <template>
<v-container max-width="800">
<v-row>
<v-col>
<card-view
:title="$t('category', 2)"
icon="mdi-label"
:subtitle="productStore.categories.length + ' ' + $t('category', productStore.categories.length)"
>
<v-data-table
:items="productStore.categories"
:headers="headers"
>
<template v-slot:item.icon="{ item }">
<v-icon :icon="item.icon" />
</template>
</v-data-table>
</card-view>
</v-col>
</v-row>
</v-container>
</template> </template>

View File

@@ -1,12 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import cardView from '@/components/cardView.vue'; import cardView from '@/components/cardView.vue';
import { ProductModel } from '@/data/models/productModel';
import { useProductStore } from '@/data/stores/productStore';
import productEditDialog from './productEditDialog.vue'; import productEditDialog from './productEditDialog.vue';
import { ref } from 'vue'; import { ref } from 'vue';
const productStore = useProductStore() // const productStore = useProductStore()
const editProduct = ref(new ProductModel()) // const editProduct = ref(new ProductModel())
const showEditProductDialog = ref(false) const showEditProductDialog = ref(false)
const headers = [ const headers = [
@@ -20,16 +18,16 @@ const headers = [
{ title: "Edit", value: "edit" }, { title: "Edit", value: "edit" },
] ]
function openEditProductDialog(product: ProductModel) { // function openEditProductDialog(product: ProductModel) {
editProduct.value = product // editProduct.value = product
showEditProductDialog.value = true // showEditProductDialog.value = true
} // }
</script> </script>
<template> <template>
<v-container> <v-container>
<v-row> <v-row>
<v-col> <!-- <v-col>
<card-view <card-view
:title="$t('product.product', 2)" :title="$t('product.product', 2)"
:subtitle="productStore.products.length + ' ' + $t('product.product', productStore.products.length)" :subtitle="productStore.products.length + ' ' + $t('product.product', productStore.products.length)"
@@ -88,11 +86,11 @@ function openEditProductDialog(product: ProductModel) {
</template> </template>
</v-data-table> </v-data-table>
</card-view> </card-view>
</v-col> </v-col> -->
</v-row> </v-row>
</v-container> </v-container>
<product-edit-dialog <!-- <product-edit-dialog
v-model="showEditProductDialog" v-model="showEditProductDialog"
:edit-product="editProduct" /> :edit-product="editProduct" /> -->
</template> </template>

View File

@@ -1,53 +0,0 @@
<script setup lang="ts">
import productCard from "./productCard.vue"
import productDetails from "./productDetailsDialog.vue"
import { ref, watch } from "vue";
import { useProductStore } from "@/data/stores/productStore";
import filterNavDrawer from "./filterNavDrawer.vue";
import { ProductModel } from '@/data/models/productModel';
const productStore = useProductStore()
const showProductDetails = ref(false)
const dialogProduct = ref(new ProductModel())
function showProductDialog(product: ProductModel) {
dialogProduct.value = product
showProductDetails.value = true
}
watch(() => productStore.filteredCategory, async () => { productStore.filterProducts() })
watch(() => productStore.sortOrder, async () => { productStore.sortProducts() })
watch(() => productStore.onlyDiscounts, async () => { productStore.filterProducts() })
</script>
<template>
<v-container max-width="1000">
<v-row dense>
<v-col
v-if="productStore.filteredProducts.length > 0"
v-for="product in productStore.filteredProducts"
cols="12"
>
<product-card
:product="product"
@click="showProductDialog(product)"
/>
</v-col>
<v-col v-else>
<v-empty-state
icon="mdi-magnify"
headline="Keine Artikel gefunden"
/>
</v-col>
</v-row>
</v-container>
<filter-nav-drawer />
<product-details
v-model="showProductDetails"
:product="dialogProduct"
/>
</template>

View File

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

View File

@@ -1,8 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { SortOrder } from '@/data/enums/sortOrderEnum'; import { SortOrder } from '@/data/enums/sortOrderEnum';
import { useProductStore } from '@/data/stores/productStore'; import { useTourStore } from '@/data/stores/tourStore';
const productStore = useProductStore() const tourStore = useTourStore()
const sortOrderItems = Object.values(SortOrder) const sortOrderItems = Object.values(SortOrder)
</script> </script>
@@ -11,15 +11,10 @@ const sortOrderItems = Object.values(SortOrder)
<v-list> <v-list>
<v-list-subheader>Filter</v-list-subheader> <v-list-subheader>Filter</v-list-subheader>
<v-list-item> <!-- <v-list-item>
<v-checkbox :label="$t('offers')" v-model="productStore.onlyDiscounts" />
</v-list-item>
<v-list-item>
<v-select <v-select
:items="productStore.categories" :items="tourStore.genres"
:label="$t('category', 2)" :label="$t('category', 2)"
v-model="productStore.filteredCategory"
> >
<template v-slot:item="{ props, item }"> <template v-slot:item="{ props, item }">
<v-list-item v-bind="props" :prepend-icon="item.raw.icon" :title="item.raw.name" /> <v-list-item v-bind="props" :prepend-icon="item.raw.icon" :title="item.raw.name" />
@@ -29,12 +24,12 @@ const sortOrderItems = Object.values(SortOrder)
<v-list-item :prepend-icon="item.raw.icon" :title="item.raw.name" /> <v-list-item :prepend-icon="item.raw.icon" :title="item.raw.name" />
</template> </template>
</v-select> </v-select>
</v-list-item> </v-list-item> -->
<v-divider /> <v-divider />
<v-list-subheader>Sort</v-list-subheader> <v-list-subheader>Sort</v-list-subheader>
<v-list-item> <!-- <v-list-item>
<v-select :label="$t('sortBy')" :items="sortOrderItems" v-model="productStore.sortOrder" > <v-select :label="$t('sortBy')" :items="sortOrderItems" v-model="productStore.sortOrder" >
<template v-slot:item="{ props, item }"> <template v-slot:item="{ props, item }">
<v-list-item v-bind="props" :title="item.title" /> <v-list-item v-bind="props" :title="item.title" />
@@ -44,7 +39,7 @@ const sortOrderItems = Object.values(SortOrder)
<v-list-item :title="item.title" /> <v-list-item :title="item.title" />
</template> </template>
</v-select> </v-select>
</v-list-item> </v-list-item> -->
</v-list> </v-list>
</v-navigation-drawer> </v-navigation-drawer>
</template> </template>

View File

@@ -0,0 +1,50 @@
<script setup lang="ts">
import tourCard from "./tourCard.vue"
import { ref, watch } from "vue";
import filterNavDrawer from "./filterNavDrawer.vue";
import { useTourStore } from "@/data/stores/tourStore";
const tourStore = useTourStore()
const showProductDetails = ref(false)
// const dialogProduct = ref(new ProductModel())
// function showProductDialog(product: ProductModel) {
// dialogProduct.value = product
// showProductDetails.value = true
// }
// watch(() => productStore.filteredCategory, async () => { productStore.filterProducts() })
// watch(() => productStore.sortOrder, async () => { productStore.sortProducts() })
// watch(() => productStore.onlyDiscounts, async () => { productStore.filterProducts() })
</script>
<template>
<v-container max-width="1200">
<v-row dense>
<v-col
v-if="tourStore.tours.length > 0"
v-for="tour in tourStore.tours"
cols="12"
>
<tour-card
:tour="tour"
/>
</v-col>
<v-col v-else>
<v-empty-state
icon="mdi-magnify"
headline="Keine Artikel gefunden"
/>
</v-col>
</v-row>
</v-container>
<filter-nav-drawer />
<!-- <product-details
v-model="showProductDetails"
:product="dialogProduct"
/> -->
</template>

View File

@@ -1,35 +1,33 @@
<script setup lang="ts"> <script setup lang="ts">
import { ProductModel } from '@/data/models/productModel'; import { TourModel } from '@/data/models/tourModel';
import cardView from '@/components/cardView.vue'; import cardView from '@/components/cardView.vue';
import OutlinedButton from '@/components/outlinedButton.vue';
defineProps({ defineProps({
product: { tour: {
required: true, required: true,
type: ProductModel type: TourModel
} }
}) })
</script> </script>
<template> <template>
<card-view link> <card-view
<template #withoutContainer> :title="tour.band.name"
<v-row> :subtitle="tour.name"
<v-col cols="3"> :prepend-image="'http://127.0.0.1:3000/static/bands/' + tour.band.images[0]"
<v-sheet color="white"> link
<v-img
:src="'http://127.0.0.1:3000/static/' + product.images[0]"
height="200"
> >
<template #placeholder> {{ tour.band.descriptionDe }}
<v-skeleton-loader type="image" />
</template>
</v-img>
</v-sheet>
</v-col>
<v-col cols="7" class="pl-0 pt-5"> <template #actions>
<div class="text-h6">{{ product.name }}</div> <OutlinedButton>
<div> {{ tour.shows.length }} {{ $t('tours.concert', tour.shows.length) }}
</OutlinedButton>
</template>
<!-- <template> -->
<!-- <div>
<v-rating <v-rating
size="medium" size="medium"
:model-value="product.rating" :model-value="product.rating"
@@ -40,19 +38,19 @@ defineProps({
/> />
{{ product.rating }}/5 {{ product.rating }}/5
</div> </div> -->
<div> <!-- <div>
<v-list class="pa-0"> <v-list class="pa-0">
<v-list-item v-for="i in 2" class="pa-0 ma-0"> <v-list-item v-for="i in 2" class="pa-0 ma-0">
<v-icon icon="mdi-circle-small" /> {{ product.specs[i - 1] }} <v-icon icon="mdi-circle-small" /> {{ product.specs[i - 1] }}
</v-list-item> </v-list-item>
</v-list> </v-list>
</div> </div> -->
</v-col> <!-- </v-col> -->
<v-col cols="2" class="pt-5 pr-7"> <!-- <v-col cols="2" class="pt-5 pr-7"> -->
<div v-if="product.discount == 0" class="font-weight-bold text-h5 text-right"> <!-- <div v-if="product.discount == 0" class="font-weight-bold text-h5 text-right">
{{ product.price }} {{ product.price }}
</div> </div>
@@ -63,9 +61,9 @@ defineProps({
<div> <div>
<div class="text-decoration-line-through text-right">{{ product.price }} </div> <div class="text-decoration-line-through text-right">{{ product.price }} </div>
</div> </div>
</div> </div> -->
<div style="position: absolute; bottom: 0; right: 0;" class="pr-2 pb-2"> <!-- <div style="position: absolute; bottom: 0; right: 0;" class="pr-2 pb-2">
<div v-if="product.inStock > 5" class="text-green-lighten-1"> <div v-if="product.inStock > 5" class="text-green-lighten-1">
{{ $t("product.storedItemsAvailable", [product.inStock]) }} {{ $t("product.storedItemsAvailable", [product.inStock]) }}
</div> </div>
@@ -75,10 +73,8 @@ defineProps({
<div v-else class="text-red"> <div v-else class="text-red">
{{ $t("product.soldOut") }} {{ $t("product.soldOut") }}
</div> </div>
</div> </div> -->
</v-col> <!-- </template> -->
</v-row>
</template>
</card-view> </card-view>
</template> </template>

View File

@@ -1,7 +1,7 @@
import AccountPage from "@/pages/accountPage/index.vue"; import AccountPage from "@/pages/accountPage/index.vue";
import OrdersPage from "@/pages/ordersPage/index.vue"; import OrdersPage from "@/pages/ordersPage/index.vue";
import PreferencesPage from "@/pages/preferencesPage/index.vue"; import PreferencesPage from "@/pages/preferencesPage/index.vue";
import ProductsPage from "@/pages/productsPage/index.vue"; import TourPage from "@/pages/toursPage/index.vue";
import LoginPage from "@/pages/loginPage/index.vue" import LoginPage from "@/pages/loginPage/index.vue"
import BasketPage from "@/pages/basketPage/index.vue" import BasketPage from "@/pages/basketPage/index.vue"
import HelpPage from "@/pages/helpPage/index.vue" import HelpPage from "@/pages/helpPage/index.vue"
@@ -9,7 +9,7 @@ import ScoreBoardPage from "@/pages/scoreBoardPage/index.vue"
import adminRoutes from "./admin.routes"; import adminRoutes from "./admin.routes";
const routes = [ const routes = [
{ path: '/', component: ProductsPage }, { path: '/', component: TourPage },
{ path: '/account', component: AccountPage }, { path: '/account', component: AccountPage },
{ path: '/orders', component: OrdersPage }, { path: '/orders', component: OrdersPage },
{ path: '/preferences', component: PreferencesPage }, { path: '/preferences', component: PreferencesPage },