Refactor frontend, display tours with cards on ToursPage
This commit is contained in:
@@ -38,6 +38,50 @@
|
||||
"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 O’Brien (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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -3,6 +3,10 @@
|
||||
{
|
||||
"id": 0,
|
||||
"name": "Funk rock"
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Art rock"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -15,6 +15,30 @@
|
||||
"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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
software/backend/images/bands/radiohead-logo.jpg
Normal file
BIN
software/backend/images/bands/radiohead-logo.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
BIN
software/backend/images/bands/radiohead.jpg
Normal file
BIN
software/backend/images/bands/radiohead.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 177 KiB |
@@ -4,21 +4,19 @@ import { i18n } from './plugins/i18n';
|
||||
import { ref, watch } from 'vue';
|
||||
import vuetify from './plugins/vuetify';
|
||||
import navigationItems from './components/navigationItems.vue';
|
||||
import { useProductStore } from './data/stores/productStore';
|
||||
import { usePreferencesStore } from './data/stores/preferencesStore';
|
||||
import { useFeedbackStore } from './data/stores/feedbackStore';
|
||||
import { useTourStore } from './data/stores/tourStore';
|
||||
|
||||
const preferencesStore = usePreferencesStore()
|
||||
const productStore = useProductStore()
|
||||
const tourStore = useTourStore()
|
||||
const feedbackStore = useFeedbackStore()
|
||||
const theme = useTheme()
|
||||
const navRail = ref(vuetify.display.mobile)
|
||||
|
||||
theme.global.name.value = preferencesStore.theme
|
||||
|
||||
productStore.fetchAllProducts()
|
||||
productStore.fetchAllCategories()
|
||||
productStore.fetchAllBrands()
|
||||
tourStore.fetchAllTours()
|
||||
|
||||
// Global watcher
|
||||
watch(() => preferencesStore.language, () => {
|
||||
|
||||
@@ -7,28 +7,72 @@ defineProps({
|
||||
},
|
||||
subtitle: {
|
||||
type: String,
|
||||
},
|
||||
prependImage: {
|
||||
type: String,
|
||||
default: ""
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-card
|
||||
:title="title"
|
||||
:subtitle="subtitle"
|
||||
:prepend-icon="icon"
|
||||
>
|
||||
<!-- Show default container only, if there is content -->
|
||||
<v-container v-if="$slots.default">
|
||||
<v-card>
|
||||
<v-row v-if="prependImage != ''">
|
||||
<v-col cols="3" class="pr-0">
|
||||
<v-img
|
||||
:src="prependImage"
|
||||
height="220"
|
||||
cover
|
||||
>
|
||||
<template #placeholder>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<v-card-actions v-if="$slots.actions" class="card-actions position-absolute bottom-0 right-0">
|
||||
<v-spacer />
|
||||
<slot name="actions"></slot>
|
||||
</v-card-actions>
|
||||
</v-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 name="withoutContainer"></slot> -->
|
||||
|
||||
<!-- Slot for Action Buttons in the right bottom corner -->
|
||||
<v-card-actions v-if="$slots.actions" class="card-actions">
|
||||
<!-- <v-card-actions v-if="$slots.actions" class="card-actions">
|
||||
<v-spacer />
|
||||
<slot name="actions"></slot>
|
||||
</v-card-actions>
|
||||
</v-card-actions> -->
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
@@ -12,12 +12,12 @@ const navRail = defineModel("navRail", { type: Boolean })
|
||||
<!-- Shopping Section -->
|
||||
|
||||
<v-list-subheader>
|
||||
<div v-if="!navRail">{{ $t('menu.shopping') }}</div>
|
||||
<div v-if="!navRail">{{ $t('menu.shopping.shopping') }}</div>
|
||||
<div v-else></div>
|
||||
</v-list-subheader>
|
||||
|
||||
<v-list-item :title="$t('menu.products')" prepend-icon="mdi-store" to="/" link />
|
||||
<v-list-item :title="$t('menu.basket')" to="/basket" link >
|
||||
<v-list-item :title="$t('menu.shopping.ticket', 2)" prepend-icon="mdi-ticket" to="/" link />
|
||||
<v-list-item :title="$t('menu.shopping.basket')" to="/basket" link >
|
||||
<template v-slot:prepend>
|
||||
<v-badge color="primary" :content="basketStore.itemsInBasket.length">
|
||||
<v-icon icon="mdi-cart" />
|
||||
@@ -31,21 +31,21 @@ const navRail = defineModel("navRail", { type: Boolean })
|
||||
<!-- Account Section -->
|
||||
|
||||
<v-list-subheader>
|
||||
<div v-if="!navRail">{{ $t('menu.account') }}</div>
|
||||
<div v-if="!navRail">{{ $t('menu.account.accountManagement') }}</div>
|
||||
<div v-else></div>
|
||||
</v-list-subheader>
|
||||
|
||||
<v-expand-transition>
|
||||
<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>
|
||||
</v-expand-transition>
|
||||
|
||||
<v-expand-transition>
|
||||
<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.orders')" 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.account')" prepend-icon="mdi-account" to="/account" link />
|
||||
<v-list-item :title="$t('menu.account.order', 2)" prepend-icon="mdi-cart-check" to="/orders" link />
|
||||
<v-list-item :title="$t('menu.account.logout')" prepend-icon="mdi-logout" @click="accountStore.logout" link />
|
||||
</div>
|
||||
</v-expand-transition>
|
||||
|
||||
@@ -55,12 +55,12 @@ const navRail = defineModel("navRail", { type: Boolean })
|
||||
<!-- System and help section -->
|
||||
|
||||
<v-list-subheader>
|
||||
<div v-if="!navRail">{{ $t('menu.systemAndHelp') }}</div>
|
||||
<div v-if="!navRail">{{ $t('menu.systemAndHelp.systemAndHelp') }}</div>
|
||||
<div v-else></div>
|
||||
</v-list-subheader>
|
||||
<v-list-item :title="$t('menu.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.preferences')" prepend-icon="mdi-cog" to="/preferences" link />
|
||||
<v-list-item :title="$t('menu.systemAndHelp.helpInstructions')" prepend-icon="mdi-chat-question" to="/help" link />
|
||||
<v-list-item :title="$t('menu.systemAndHelp.scoreBoard')" prepend-icon="mdi-podium-gold" to="/scoreBoard" link />
|
||||
<v-list-item :title="$t('menu.systemAndHelp.preferences')" prepend-icon="mdi-cog" to="/preferences" link />
|
||||
|
||||
|
||||
<div v-if="accountStore.userAccount.accountRole.privilegeAdminPanel">
|
||||
@@ -72,10 +72,6 @@ const navRail = defineModel("navRail", { type: Boolean })
|
||||
</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.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>
|
||||
|
||||
</v-list>
|
||||
|
||||
11
software/src/data/api/bandApi.ts
Normal file
11
software/src/data/api/bandApi.ts
Normal 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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
7
software/src/data/api/genreApi.ts
Normal file
7
software/src/data/api/genreApi.ts
Normal 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)
|
||||
}
|
||||
7
software/src/data/api/locationApi.ts
Normal file
7
software/src/data/api/locationApi.ts
Normal 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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
7
software/src/data/api/showApi.ts
Normal file
7
software/src/data/api/showApi.ts
Normal 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)
|
||||
}
|
||||
10
software/src/data/api/tourApi.ts
Normal file
10
software/src/data/api/tourApi.ts
Normal 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)
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
import { ProductModel } from "./productModel"
|
||||
import { ShowModel } from "./showModel"
|
||||
|
||||
export class BasketItemModel {
|
||||
id: 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.product = product
|
||||
}
|
||||
|
||||
5
software/src/data/models/cityModel.ts
Normal file
5
software/src/data/models/cityModel.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export class CityModel {
|
||||
id: Number
|
||||
name: String
|
||||
country: String
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
import { CityModel } from "./cityModel"
|
||||
|
||||
export class LocationModel {
|
||||
id: Number
|
||||
name: String
|
||||
address: String
|
||||
city: String
|
||||
city: CityModel
|
||||
image: String
|
||||
}
|
||||
@@ -3,6 +3,5 @@ import { BandModel } from "./bandModel"
|
||||
export class MemberModel {
|
||||
id: Number
|
||||
name: String
|
||||
band: BandModel
|
||||
image: String
|
||||
}
|
||||
@@ -3,7 +3,6 @@ import { BandModel } from "./bandModel"
|
||||
|
||||
export class RatingModel {
|
||||
id: Number
|
||||
account: AccountModel
|
||||
rating: Number
|
||||
band: BandModel
|
||||
}
|
||||
@@ -2,9 +2,9 @@ import { BandModel } from "./bandModel"
|
||||
import { ShowModel } from "./showModel"
|
||||
|
||||
export class TourModel {
|
||||
id: Number
|
||||
name: String
|
||||
id: number
|
||||
name: string
|
||||
offered: boolean
|
||||
band: BandModel
|
||||
offered: Boolean
|
||||
shows: Array<ShowModel>
|
||||
}
|
||||
@@ -6,8 +6,7 @@ import { useFeedbackStore } from "./feedbackStore";
|
||||
import { BannerStateEnum } from "../enums/bannerStateEnum";
|
||||
import { addOrder } from "../api/orderApi";
|
||||
import { useAccountStore } from "./accountStore";
|
||||
import { ProductModel } from "../models/productModel";
|
||||
import { useProductStore } from "./productStore";
|
||||
import { ShowModel } from "../models/showModel";
|
||||
import { AddressModel } from "../models/addressModel";
|
||||
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
|
||||
*
|
||||
* @param product Product to add
|
||||
* @param show Show to add
|
||||
* @param quantity Quantity of the product
|
||||
*/
|
||||
addItemToBasket(product: ProductModel, quantity: number) {
|
||||
addItemToBasket(show: ShowModel, 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 == product.id))
|
||||
basketItem.product.id == show.id))
|
||||
{
|
||||
this.itemsInBasket.find((basketItem: BasketItemModel) =>
|
||||
basketItem.product.id == product.id).quantity += quantity
|
||||
basketItem.product.id == show.id).quantity += quantity
|
||||
} 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
|
||||
*/
|
||||
async takeOrder() {
|
||||
const accountStore = useAccountStore()
|
||||
const productStore = useProductStore()
|
||||
const feedbackStore = useFeedbackStore()
|
||||
// todo
|
||||
// const accountStore = useAccountStore()
|
||||
// const productStore = useProductStore()
|
||||
// const feedbackStore = useFeedbackStore()
|
||||
|
||||
await addOrder(accountStore.userAccount.id, this.itemsInBasket, this.usedPayment.id, this.usedAddress.id)
|
||||
.then(async result => {
|
||||
if (result.status == 201) {
|
||||
await accountStore.refreshOrders()
|
||||
await productStore.fetchAllProducts()
|
||||
// await addOrder(accountStore.userAccount.id, this.itemsInBasket, this.usedPayment.id, this.usedAddress.id)
|
||||
// .then(async result => {
|
||||
// if (result.status == 201) {
|
||||
// await accountStore.refreshOrders()
|
||||
// await productStore.fetchAllProducts()
|
||||
|
||||
this.itemsInBasket = []
|
||||
feedbackStore.changeBanner(BannerStateEnum.ORDERPLACESUCCESSFUL)
|
||||
} else {
|
||||
feedbackStore.changeBanner(BannerStateEnum.ERROR)
|
||||
}
|
||||
})
|
||||
// this.itemsInBasket = []
|
||||
// feedbackStore.changeBanner(BannerStateEnum.ORDERPLACESUCCESSFUL)
|
||||
// } else {
|
||||
// feedbackStore.changeBanner(BannerStateEnum.ERROR)
|
||||
// }
|
||||
// })
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
21
software/src/data/stores/tourStore.ts
Normal file
21
software/src/data/stores/tourStore.ts
Normal 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
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -1,24 +1,32 @@
|
||||
{
|
||||
"menu": {
|
||||
"shopping": "Einkaufen",
|
||||
"products": "Produkte",
|
||||
"basket": "Warenkorb",
|
||||
"login": "Login",
|
||||
"account": "Account",
|
||||
"orders": "Bestellungen",
|
||||
"systemAndHelp": "System & Hilfe",
|
||||
"helpInstructions": "Hilfestellung",
|
||||
"preferences": "Einstellungen",
|
||||
"logout": "Ausloggen",
|
||||
"scoreBoard": "Score Board",
|
||||
"shopping": {
|
||||
"shopping": "Einkaufen",
|
||||
"ticket": "Ticket | Tickets",
|
||||
"basket": "Warenkorb"
|
||||
},
|
||||
"account": {
|
||||
"accountManagement": "Kontoverwaltung",
|
||||
"account": "Account",
|
||||
"login": "Login",
|
||||
"order": "Bestellung | Bestellungen",
|
||||
"logout": "Ausloggen"
|
||||
},
|
||||
"systemAndHelp": {
|
||||
"systemAndHelp": "System & Hilfe",
|
||||
"helpInstructions": "Hilfestellung",
|
||||
"preferences": "Einstellungen",
|
||||
"scoreBoard": "Score Board"
|
||||
},
|
||||
"admin": {
|
||||
"admin": "Administration",
|
||||
"dashboard": "Dashboard",
|
||||
"categories": "Kategorien",
|
||||
"products": "Produkte",
|
||||
"accounts": "Accounts"
|
||||
"dashboard": "Dashboard"
|
||||
}
|
||||
},
|
||||
"tours": {
|
||||
"concert": "Konzert | Konzerte"
|
||||
},
|
||||
|
||||
"preferences": {
|
||||
"pageSetup": "Seiteneinstellungen",
|
||||
"selectedTheme": "Ausgewähltes Theme",
|
||||
|
||||
@@ -1,24 +1,33 @@
|
||||
{
|
||||
"menu": {
|
||||
"shopping": "Shopping",
|
||||
"products": "Products",
|
||||
"basket": "Basket",
|
||||
"login": "Login",
|
||||
"account": "Account",
|
||||
"orders": "Orders",
|
||||
"systemAndHelp": "System & Help",
|
||||
"helpInstructions": "Help instructions",
|
||||
"preferences": "Preferences",
|
||||
"logout": "Logout",
|
||||
"scoreBoard": "Score Board",
|
||||
"shopping": {
|
||||
"shopping": "Shopping",
|
||||
"ticket": "Ticket | Tickets",
|
||||
"basket": "Basket"
|
||||
},
|
||||
"account": {
|
||||
"account": "Account Management",
|
||||
"login": "Login",
|
||||
"order": "Order | Orders",
|
||||
"logout": "Logout",
|
||||
"accountManagement": "Account Management"
|
||||
},
|
||||
"systemAndHelp": {
|
||||
"systemAndHelp": "System & Help",
|
||||
"helpInstructions": "Help Instructions",
|
||||
"preferences": "Preferences",
|
||||
"scoreBoard": "Score Board"
|
||||
},
|
||||
"admin": {
|
||||
"admin": "Administration",
|
||||
"dashboard": "Dashboard",
|
||||
"categories": "Categories",
|
||||
"products": "Products",
|
||||
"accounts": "Accounts"
|
||||
"dashboard": "Dashboard"
|
||||
}
|
||||
},
|
||||
"tours": {
|
||||
"concert": "Concert | Concerts"
|
||||
},
|
||||
|
||||
|
||||
"preferences": {
|
||||
"pageSetup": "Page setup",
|
||||
"selectedTheme": "Selected theme",
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
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 = [
|
||||
{ title: "Name", value: "name" },
|
||||
@@ -11,7 +11,7 @@ const headers = [
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-container max-width="800">
|
||||
<!-- <v-container max-width="800">
|
||||
<v-row>
|
||||
<v-col>
|
||||
<card-view
|
||||
@@ -24,9 +24,8 @@ const headers = [
|
||||
:headers="headers"
|
||||
>
|
||||
</v-data-table>
|
||||
<!-- todo: Edit/Delete -->
|
||||
</card-view>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</v-container> -->
|
||||
</template>
|
||||
@@ -1,8 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import cardView from '@/components/cardView.vue';
|
||||
import { useProductStore } from '@/data/stores/productStore';
|
||||
|
||||
const productStore = useProductStore()
|
||||
|
||||
const headers = [
|
||||
{ title: "Name", value: "name" },
|
||||
@@ -12,24 +9,5 @@ const headers = [
|
||||
</script>
|
||||
|
||||
<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>
|
||||
@@ -1,12 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import cardView from '@/components/cardView.vue';
|
||||
import { ProductModel } from '@/data/models/productModel';
|
||||
import { useProductStore } from '@/data/stores/productStore';
|
||||
import productEditDialog from './productEditDialog.vue';
|
||||
import { ref } from 'vue';
|
||||
|
||||
const productStore = useProductStore()
|
||||
const editProduct = ref(new ProductModel())
|
||||
// const productStore = useProductStore()
|
||||
// const editProduct = ref(new ProductModel())
|
||||
const showEditProductDialog = ref(false)
|
||||
|
||||
const headers = [
|
||||
@@ -20,16 +18,16 @@ const headers = [
|
||||
{ title: "Edit", value: "edit" },
|
||||
]
|
||||
|
||||
function openEditProductDialog(product: ProductModel) {
|
||||
editProduct.value = product
|
||||
showEditProductDialog.value = true
|
||||
}
|
||||
// function openEditProductDialog(product: ProductModel) {
|
||||
// editProduct.value = product
|
||||
// showEditProductDialog.value = true
|
||||
// }
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-container>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<!-- <v-col>
|
||||
<card-view
|
||||
:title="$t('product.product', 2)"
|
||||
:subtitle="productStore.products.length + ' ' + $t('product.product', productStore.products.length)"
|
||||
@@ -88,11 +86,11 @@ function openEditProductDialog(product: ProductModel) {
|
||||
</template>
|
||||
</v-data-table>
|
||||
</card-view>
|
||||
</v-col>
|
||||
</v-col> -->
|
||||
</v-row>
|
||||
</v-container>
|
||||
|
||||
<product-edit-dialog
|
||||
<!-- <product-edit-dialog
|
||||
v-model="showEditProductDialog"
|
||||
:edit-product="editProduct" />
|
||||
:edit-product="editProduct" /> -->
|
||||
</template>
|
||||
@@ -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>
|
||||
6
software/src/pages/tourDetailPage/index.vue
Normal file
6
software/src/pages/tourDetailPage/index.vue
Normal file
@@ -0,0 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
</template>
|
||||
@@ -1,8 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
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)
|
||||
</script>
|
||||
|
||||
@@ -11,15 +11,10 @@ const sortOrderItems = Object.values(SortOrder)
|
||||
<v-list>
|
||||
<v-list-subheader>Filter</v-list-subheader>
|
||||
|
||||
<v-list-item>
|
||||
<v-checkbox :label="$t('offers')" v-model="productStore.onlyDiscounts" />
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item>
|
||||
<!-- <v-list-item>
|
||||
<v-select
|
||||
:items="productStore.categories"
|
||||
:items="tourStore.genres"
|
||||
:label="$t('category', 2)"
|
||||
v-model="productStore.filteredCategory"
|
||||
>
|
||||
<template v-slot:item="{ props, item }">
|
||||
<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" />
|
||||
</template>
|
||||
</v-select>
|
||||
</v-list-item>
|
||||
</v-list-item> -->
|
||||
|
||||
<v-divider />
|
||||
<v-list-subheader>Sort</v-list-subheader>
|
||||
|
||||
<v-list-item>
|
||||
<!-- <v-list-item>
|
||||
<v-select :label="$t('sortBy')" :items="sortOrderItems" v-model="productStore.sortOrder" >
|
||||
<template v-slot:item="{ props, item }">
|
||||
<v-list-item v-bind="props" :title="item.title" />
|
||||
@@ -44,7 +39,7 @@ const sortOrderItems = Object.values(SortOrder)
|
||||
<v-list-item :title="item.title" />
|
||||
</template>
|
||||
</v-select>
|
||||
</v-list-item>
|
||||
</v-list-item> -->
|
||||
</v-list>
|
||||
</v-navigation-drawer>
|
||||
</template>
|
||||
50
software/src/pages/toursPage/index.vue
Normal file
50
software/src/pages/toursPage/index.vue
Normal 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>
|
||||
@@ -1,35 +1,33 @@
|
||||
<script setup lang="ts">
|
||||
import { ProductModel } from '@/data/models/productModel';
|
||||
import { TourModel } from '@/data/models/tourModel';
|
||||
import cardView from '@/components/cardView.vue';
|
||||
import OutlinedButton from '@/components/outlinedButton.vue';
|
||||
|
||||
defineProps({
|
||||
product: {
|
||||
tour: {
|
||||
required: true,
|
||||
type: ProductModel
|
||||
type: TourModel
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<card-view link>
|
||||
<template #withoutContainer>
|
||||
<v-row>
|
||||
<v-col cols="3">
|
||||
<v-sheet color="white">
|
||||
<v-img
|
||||
:src="'http://127.0.0.1:3000/static/' + product.images[0]"
|
||||
height="200"
|
||||
>
|
||||
<template #placeholder>
|
||||
<v-skeleton-loader type="image" />
|
||||
</template>
|
||||
</v-img>
|
||||
</v-sheet>
|
||||
</v-col>
|
||||
<card-view
|
||||
:title="tour.band.name"
|
||||
:subtitle="tour.name"
|
||||
:prepend-image="'http://127.0.0.1:3000/static/bands/' + tour.band.images[0]"
|
||||
link
|
||||
>
|
||||
{{ tour.band.descriptionDe }}
|
||||
|
||||
<v-col cols="7" class="pl-0 pt-5">
|
||||
<div class="text-h6">{{ product.name }}</div>
|
||||
<div>
|
||||
<template #actions>
|
||||
<OutlinedButton>
|
||||
{{ tour.shows.length }} {{ $t('tours.concert', tour.shows.length) }}
|
||||
</OutlinedButton>
|
||||
</template>
|
||||
|
||||
<!-- <template> -->
|
||||
<!-- <div>
|
||||
<v-rating
|
||||
size="medium"
|
||||
:model-value="product.rating"
|
||||
@@ -40,19 +38,19 @@ defineProps({
|
||||
/>
|
||||
|
||||
{{ product.rating }}/5
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<div>
|
||||
<!-- <div>
|
||||
<v-list class="pa-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-list-item>
|
||||
</v-list>
|
||||
</div>
|
||||
</v-col>
|
||||
</div> -->
|
||||
<!-- </v-col> -->
|
||||
|
||||
<v-col cols="2" class="pt-5 pr-7">
|
||||
<div v-if="product.discount == 0" class="font-weight-bold text-h5 text-right">
|
||||
<!-- <v-col cols="2" class="pt-5 pr-7"> -->
|
||||
<!-- <div v-if="product.discount == 0" class="font-weight-bold text-h5 text-right">
|
||||
{{ product.price }} €
|
||||
</div>
|
||||
|
||||
@@ -63,9 +61,9 @@ defineProps({
|
||||
<div>
|
||||
<div class="text-decoration-line-through text-right">{{ product.price }} €</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">
|
||||
{{ $t("product.storedItemsAvailable", [product.inStock]) }}
|
||||
</div>
|
||||
@@ -75,10 +73,8 @@ defineProps({
|
||||
<div v-else class="text-red">
|
||||
{{ $t("product.soldOut") }}
|
||||
</div>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
</div> -->
|
||||
<!-- </template> -->
|
||||
</card-view>
|
||||
</template>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import AccountPage from "@/pages/accountPage/index.vue";
|
||||
import OrdersPage from "@/pages/ordersPage/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 BasketPage from "@/pages/basketPage/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";
|
||||
|
||||
const routes = [
|
||||
{ path: '/', component: ProductsPage },
|
||||
{ path: '/', component: TourPage },
|
||||
{ path: '/account', component: AccountPage },
|
||||
{ path: '/orders', component: OrdersPage },
|
||||
{ path: '/preferences', component: PreferencesPage },
|
||||
|
||||
Reference in New Issue
Block a user