Redesign productDetail dialog

This commit is contained in:
2024-09-20 15:08:17 +02:00
parent 88c43d62c6
commit 871f8cac7a
18 changed files with 230 additions and 174 deletions

View File

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

View File

@@ -5,99 +5,116 @@
"brand": "Lenovo", "brand": "Lenovo",
"name": "Thinkpad T14", "name": "Thinkpad T14",
"price": 799.99, "price": 799.99,
"categoryId": 0, "categoryId": 1,
"discount": 10, "discount": 10,
"rating": 4.6, "rating": 4.6,
"description": "Die stabile Arbeitsmaschine. Mit AMD Ryzen 7 89029U, 128 GB RAM und 8 TB M.2 SSD!", "description": "Die stabile Arbeitsmaschine. Mit AMD Ryzen 7 89029U, 128 GB RAM und 8 TB M.2 SSD!",
"imageUrl": "thinkpad-t14s.jpg" "images": ["thinkpad-t14s.jpg"]
}, },
{ {
"id": 1, "id": 1,
"brand": "Puma", "brand": "Puma",
"name": "Men's Shirt", "name": "Men's Shirt",
"price": 14.99, "price": 14.99,
"categoryId": 2, "categoryId": 3,
"discount": 0, "discount": 0,
"rating": 3.8, "rating": 3.8,
"description": "Normale Passform, ideal für den sportlichen Alltag.", "description": "Normale Passform, ideal für den sportlichen Alltag.",
"imageUrl": "puma-t-shirt-men.avif" "images": ["puma-t-shirt-men.avif"]
}, },
{ {
"id": 2, "id": 2,
"brand": "Puma", "brand": "Puma",
"name": "Woman's Shirt", "name": "Woman's Shirt",
"price": 14.99, "price": 14.99,
"categoryId": 2, "categoryId": 3,
"discount": 0, "discount": 0,
"rating": 4.0, "rating": 4.0,
"description": "Normale Passform, ideal für den sportlichen Alltag.", "description": "Normale Passform, ideal für den sportlichen Alltag.",
"imageUrl": "puma-t-shirt-woman.avif" "images": ["puma-t-shirt-woman.avif"]
}, },
{ {
"id": 3, "id": 3,
"brand": "George Orwell", "brand": "George Orwell",
"name": "1984", "name": "1984",
"price": 9.99, "price": 9.99,
"categoryId": 3, "categoryId": 4,
"discount": 0, "discount": 0,
"rating": 4.9, "rating": 4.9,
"imageUrl": "1984.jpg", "description": "BIG BROTHER IS WATCHING YOU! George Orwells 1984 ist längst zu einer scheinbar nicht mehr erklärungsbedürftigen Metapher für totalitäre Verhältnisse geworden. Mit atemberaubender Unerbittlichkeit zeichnet der Autor das erschreckende Bild einer durch und...",
"description": "BIG BROTHER IS WATCHING YOU! George Orwells 1984 ist längst zu einer scheinbar nicht mehr erklärungsbedürftigen Metapher für totalitäre Verhältnisse geworden. Mit atemberaubender Unerbittlichkeit zeichnet der Autor das erschreckende Bild einer durch und..." "images": ["1984.jpg"]
}, },
{ {
"id": 4, "id": 4,
"brand": "Johann W. Goethe", "brand": "Johann W. Goethe",
"name": "Faust", "name": "Faust",
"price": 4.99, "price": 4.99,
"categoryId": 3, "categoryId": 4,
"discount": 0, "discount": 0,
"rating": 4.2, "rating": 4.2,
"description": "Goethe schrieb über 60 Jahre an seinem »Faust« und nannte »diese sehr ernsten Scherze« am Ende sein »Hauptgeschäft«: Dabei entstand eines der großartigsten und gleichzeitig komplexesten Werke der Weltliteratur. Den »Faust« gibt es bei Reclam in vielen Ausgaben, preisniedrig für Schüler, mit Kommentar für Studenten, bibliophil für Liebhaber jetzt endlich auch eine Doppelausgabe der beiden klassischen Theatertexte in der Universal-Bibliothek: »Faust I« und »II« in einem Band.", "description": "Goethe schrieb über 60 Jahre an seinem »Faust« und nannte »diese sehr ernsten Scherze« am Ende sein »Hauptgeschäft«: Dabei entstand eines der großartigsten und gleichzeitig komplexesten Werke der Weltliteratur. Den »Faust« gibt es bei Reclam in vielen Ausgaben, preisniedrig für Schüler, mit Kommentar für Studenten, bibliophil für Liebhaber jetzt endlich auch eine Doppelausgabe der beiden klassischen Theatertexte in der Universal-Bibliothek: »Faust I« und »II« in einem Band.",
"imageUrl": "goethe-faust.jpg" "images": ["goethe-faust.jpg"]
}, },
{ {
"id": 5, "id": 5,
"brand": "Theodor Sturm", "brand": "Theodor Sturm",
"name": "Der Schimmelreiter", "name": "Der Schimmelreiter",
"price": 4.99, "price": 4.99,
"categoryId": 3, "categoryId": 4,
"discount": 0, "discount": 0,
"rating": 3.5, "rating": 3.5,
"description": "Hauke Haien ist ein genialer Außenseiter, der sich als junger Deichgraf einen Jugendtraum erfüllt: den Bau eines neuartigen Deiches, der den Wellen besser standhalten soll. Die Dorfbewohner sind skeptisch und sehen in ihm die Verkörperung einer uralten Sage: Wenn er auf seinem Schimmel über den Deich reitet, wird Hauke Haien zum dämonischen Reiter, der ihr Leben und ihre Gesetze aus dem Gleichgewicht bringt. Theodor Storms bekannteste Novelle ist ein Meisterwerk realistischer Erzählkunst, in dem es um den Widerstreit von Rationalität und Aberglaube, Fortschritt und Tradition geht.", "description": "Hauke Haien ist ein genialer Außenseiter, der sich als junger Deichgraf einen Jugendtraum erfüllt: den Bau eines neuartigen Deiches, der den Wellen besser standhalten soll. Die Dorfbewohner sind skeptisch und sehen in ihm die Verkörperung einer uralten Sage: Wenn er auf seinem Schimmel über den Deich reitet, wird Hauke Haien zum dämonischen Reiter, der ihr Leben und ihre Gesetze aus dem Gleichgewicht bringt. Theodor Storms bekannteste Novelle ist ein Meisterwerk realistischer Erzählkunst, in dem es um den Widerstreit von Rationalität und Aberglaube, Fortschritt und Tradition geht.",
"imageUrl": "der-schimmelreiter.jpg" "images": ["der-schimmelreiter.jpg"]
}, },
{ {
"id": 6, "id": 6,
"brand": "Aldous Huxley", "brand": "Aldous Huxley",
"name": "Brave New World", "name": "Brave New World",
"price": 7.99, "price": 7.99,
"categoryId": 3, "categoryId": 4,
"discount": 0, "discount": 0,
"rating": 4.4, "rating": 4.4,
"imageUrl": "brave-new-world.jpg", "description": "Brave New World beschreibt eine genormte Gesellschaft, in der Föten genetisch manipuliert und Menschen konditioniert werden. Ziel des Staates ist Zufriedenheit und Stabilität, und dies wird durch Gleichheit, Drogen und Propaganda erreicht. Gott und Religion...",
"description": "Brave New World beschreibt eine genormte Gesellschaft, in der Föten genetisch manipuliert und Menschen konditioniert werden. Ziel des Staates ist Zufriedenheit und Stabilität, und dies wird durch Gleichheit, Drogen und Propaganda erreicht. Gott und Religion..." "images": ["brave-new-world.jpg"]
}, },
{ {
"id": 7, "id": 7,
"brand": "Ankermann", "brand": "Ankermann",
"name": "Gaming Work V3", "name": "Gaming Work V3",
"price": 1299.99, "price": 1299.99,
"categoryId": 0, "categoryId": 1,
"discount": 0, "discount": 0,
"rating": 4.7, "rating": 4.7,
"description": "Der schnellste Desktop-PC auf dem Markt! Intel Core i9 19650, 128 GB RAM, Nvidia Geforce RTX 6090. Gepaart mit 8 TB SSD Speicher in einem schicken RGB Gehäuse.", "description": "Der schnellste Desktop-PC auf dem Markt! Intel Core i9 19650, 128 GB RAM, Nvidia Geforce RTX 6090. Gepaart mit 8 TB SSD Speicher in einem schicken RGB Gehäuse.",
"imageUrl": "ankerman-gaming-work-v3.jpg" "images": ["ankerman-gaming-work-v3.jpg"]
}, },
{ {
"id": 8, "id": 8,
"brand": "Adidas", "brand": "Adidas",
"name": "UCL Pro 23/24", "name": "UCL Pro 23/24",
"price": 99.99, "price": 99.99,
"categoryId": 1, "categoryId": 2,
"discount": 0, "discount": 0,
"rating": 3.9, "rating": 3.9,
"description": "Der Adidas UCL Pro ist beim Kicken für Hobbyspieler, Amateure und Profis unsere Nummer Eins. Denn der offizielle Spielball der Champions League hat eine wunderbar weiche, aber dennoch sehr robuste und abriebfeste Oberfläche. Mehr Ballgefühl als mit dem UCL Pro kann man kaum haben. Seine Außenhaut gefällt uns noch einen Tick besser als die der Mitbewerber. Die einzelnen Panels aus Polyurethan sind thermisch verklebt. Dadurch ist der Ball nahtlos und hat eine perfekte Kugelform.", "description": "Der Adidas UCL Pro ist beim Kicken für Hobbyspieler, Amateure und Profis unsere Nummer Eins. Denn der offizielle Spielball der Champions League hat eine wunderbar weiche, aber dennoch sehr robuste und abriebfeste Oberfläche. Mehr Ballgefühl als mit dem UCL Pro kann man kaum haben. Seine Außenhaut gefällt uns noch einen Tick besser als die der Mitbewerber. Die einzelnen Panels aus Polyurethan sind thermisch verklebt. Dadurch ist der Ball nahtlos und hat eine perfekte Kugelform.",
"imageUrl": "fussball-champions-league.jpg" "images": ["fussball-champions-league.jpg"]
},
{
"id": 9,
"brand": "Fender",
"name": "Player II Jazz Bass RW 3TS",
"price": 899.99,
"categoryId": 5,
"discount": 5,
"rating": 4.8,
"description": "The Fender Player II JazzBass RW 3TS 3-Color Sunburst relies on great features that stand out in terms of comfort, playability and look, as well as providing all the advantages for a wide variety of genres. Whether dynamic bass lines or lush rock riffs: the Fender Player II Jazz Bass is a versatile E-Bass that is not only perfect for practice or studio sessions, but also functions as an assertive bass carpet in band contexts and live mixes. The robust alder body has a classic 3-color Sunburst-Finish finish and shines with easy handling and a comfortable grip - whether sitting or standing. The maple neck with a modern 'C' neck profile is fitted with a rosewood fingerboard, which is equipped with 20 medium jumbo frets and a fingerboard radius of 9.5', making it easy to use a wide variety of bass techniques and enabling quick fretting and precise riffs to be tackled cleanly.",
"images": [
"fender-player-ii-jazz-bass-rw-3ts-1.jpg",
"fender-player-ii-jazz-bass-rw-3ts-2.jpg",
"fender-player-ii-jazz-bass-rw-3ts-3.jpg",
"fender-player-ii-jazz-bass-rw-3ts-4.jpg",
"fender-player-ii-jazz-bass-rw-3ts-5.jpg"
]
} }
] ]
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@@ -1,4 +1,4 @@
import { Table, Column, Model, ForeignKey, BelongsTo, BelongsToMany, HasMany } from 'sequelize-typescript'; import { Table, Column, Model, ForeignKey, BelongsTo, BelongsToMany, HasMany, DataType } from 'sequelize-typescript';
import { Category } from './category.model'; import { Category } from './category.model';
import { OrderItem } from './orderItem.model'; import { OrderItem } from './orderItem.model';
@@ -23,8 +23,17 @@ export class Product extends Model {
@Column @Column
rating: number rating: number
@Column @Column({
imageUrl: string type: DataType.STRING,
get(): Array<string> {
return this.getDataValue('images').split(';')
},
set(value: Array<string>) {
console.log(value)
this.setDataValue('images', value.join(';'))
}
})
images: Array<string>
@Column @Column
description: string description: string

View File

@@ -5,7 +5,14 @@ const showDialog: ModelRef<boolean> = defineModel()
defineProps({ defineProps({
title: String, title: String,
subtitle: String, icon: {
type: String,
default: "mdi-cog"
},
subtitle: {
type: String,
default: ""
},
imageUrl: { imageUrl: {
type: String, type: String,
default: "" default: ""
@@ -14,21 +21,9 @@ defineProps({
</script> </script>
<template> <template>
<v-dialog max-width="1000" v-model="showDialog"> <v-dialog max-width="1200" v-model="showDialog">
<v-card :title="title" :subtitle="subtitle" > <v-card :title="title" :subtitle="subtitle" :prepend-icon="icon" >
<v-container>
<v-row>
<v-col>
<v-img v-if="imageUrl != ''" :src="imageUrl" max-height="600" />
</v-col>
<v-col>
<v-card-text>
<slot name="content"></slot> <slot name="content"></slot>
</v-card-text>
</v-col>
</v-row>
</v-container>
<v-card-actions> <v-card-actions>
<slot name="actions"></slot> <slot name="actions"></slot>

View File

@@ -15,6 +15,7 @@ const navRail = defineModel("navRail", { type: Boolean })
<div v-if="!navRail">{{ $t('menu.shopping') }}</div> <div v-if="!navRail">{{ $t('menu.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.products')" prepend-icon="mdi-store" to="/" link />
<v-list-item :title="$t('menu.basket')" to="/basket" link > <v-list-item :title="$t('menu.basket')" to="/basket" link >
<template v-slot:prepend> <template v-slot:prepend>

View File

@@ -7,5 +7,5 @@ export class ProductModel {
price: number = 0 price: number = 0
discount: number = 0 discount: number = 0
rating: number = 1 rating: number = 1
imageUrl: string = "" images: Array<string> = [""]
} }

View File

@@ -5,9 +5,9 @@ export class ProductWithCategoryModel {
brand: string brand: string
name: string name: string
description: string = "" description: string = ""
category: CategoryModel category: CategoryModel = new CategoryModel()
price: number = 0 price: number = 0
discount: number = 0 discount: number = 0
rating: number = 1 rating: number = 1
imageUrl: string = "" images: Array<string> = [""]
} }

View File

@@ -25,7 +25,7 @@ export const useProductStore = defineStore("productStore", {
}, },
async filterProducts() { async filterProducts() {
if (this.filteredCategory.id == -1) { if (this.filteredCategory.id == -1 || this.filteredCategory.id == 0) {
this.filteredProducts = this.products this.filteredProducts = this.products
} else { } else {
this.filteredProducts = this.products.filter((product: ProductWithCategoryModel) => this.filteredProducts = this.products.filter((product: ProductWithCategoryModel) =>

View File

@@ -1,58 +0,0 @@
<script setup lang="ts">
import { SortOrder } from '@/data/enums/sortOrderEnum';
import { useCategoryStore } from '@/data/stores/categoryStore';
import { useProductStore } from '@/data/stores/productStore';
const productStore = useProductStore()
const categoryStore = useCategoryStore()
const sortOrderItems = Object.values(SortOrder)
</script>
<template>
<v-card>
<v-card-title>
<div v-if="productStore.filteredProducts.length == 1">
{{ productStore.filteredProducts.length }} {{ $t('product.product') }}
</div>
<div v-else>
{{ productStore.filteredProducts.length }} {{ $t('product.products') }}
</div>
</v-card-title>
<v-container class="pb-0">
<v-row>
<v-spacer />
<v-col class="d-flex justify-center align-center">
<v-checkbox :label="$t('offers')" v-model="productStore.onlyDiscounts" />
</v-col>
<v-col class="d-flex justify-left align-center">
<v-select
:items="categoryStore.categories"
:label="$t('categories')"
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" />
</template>
<template v-slot:selection="{ item }">
<v-list-item :prepend-icon="item.raw.icon" :title="item.raw.name" />
</template>
</v-select>
</v-col>
<v-col class="d-flex justify-left align-center">
<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" />
</template>
<template v-slot:selection="{ item }">
<v-list-item :title="item.title" />
</template>
</v-select>
</v-col>
</v-row>
</v-container>
</v-card>
</template>

View File

@@ -0,0 +1,52 @@
<script setup lang="ts">
import { SortOrder } from '@/data/enums/sortOrderEnum';
import { useCategoryStore } from '@/data/stores/categoryStore';
import { useProductStore } from '@/data/stores/productStore';
const productStore = useProductStore()
const categoryStore = useCategoryStore()
const sortOrderItems = Object.values(SortOrder)
</script>
<template>
<v-navigation-drawer location="right" width="300" permanent>
<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-select
:items="categoryStore.categories"
:label="$t('categories')"
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" />
</template>
<template v-slot:selection="{ item }">
<v-list-item :prepend-icon="item.raw.icon" :title="item.raw.name" />
</template>
</v-select>
</v-list-item>
<v-divider />
<v-list-subheader>Sort</v-list-subheader>
<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" />
</template>
<template v-slot:selection="{ item }">
<v-list-item :title="item.title" />
</template>
</v-select>
</v-list-item>
</v-list>
</v-navigation-drawer>
</template>

View File

@@ -1,25 +1,17 @@
<script setup lang="ts"> <script setup lang="ts">
import productCard from "./productCard.vue" import productCard from "./productCard.vue"
import productDetails from "./productDetails.vue" import productDetails from "./productDetails.vue"
import filterBar from "./filterBar.vue"
import { ref, watch } from "vue"; import { ref, watch } from "vue";
import { FilterModel } from "@/data/models/filterModel";
import { useProductStore } from "@/data/stores/productStore"; import { useProductStore } from "@/data/stores/productStore";
import { ProductWithCategoryModel } from "@/data/models/productWithCategoryModel"; import { ProductWithCategoryModel } from "@/data/models/productWithCategoryModel";
import alertBanner from "@/components/alertBanner.vue"; import alertBanner from "@/components/alertBanner.vue";
import filterNavDrawer from "./filterNavDrawer.vue";
const productStore = useProductStore() const productStore = useProductStore()
const showProductDetails = ref(false) const showProductDetails = ref(false)
const dialogProduct = ref(new ProductWithCategoryModel()) const dialogProduct = ref(new ProductWithCategoryModel())
const sortBy: Array<FilterModel> = [
{ icon: "mdi-sort-ascending", name: "Price: Low to high" },
{ icon: "mdi-sort-descending", name: "Price: High to low" },
{ icon: "mdi-sort-alphabetical-ascending", name: "Name: A to Z" },
{ icon: "mdi-sort-alphabetical-descending", name: "Name: Z to A" },
]
function showProductDialog(product: ProductWithCategoryModel) { function showProductDialog(product: ProductWithCategoryModel) {
dialogProduct.value = product dialogProduct.value = product
showProductDetails.value = true showProductDetails.value = true
@@ -37,11 +29,7 @@ watch(() => productStore.onlyDiscounts, async () => { productStore.filterProduct
<alert-banner /> <alert-banner />
</v-col> </v-col>
</v-row> </v-row>
<v-row>
<v-col>
<filter-bar />
</v-col>
</v-row>
<v-row dense> <v-row dense>
<v-col <v-col
v-if="productStore.filteredProducts.length > 0" v-if="productStore.filteredProducts.length > 0"
@@ -63,6 +51,8 @@ watch(() => productStore.onlyDiscounts, async () => { productStore.filterProduct
</v-row> </v-row>
</v-container> </v-container>
<filter-nav-drawer />
<product-details <product-details
v-model="showProductDetails" v-model="showProductDetails"
:product="dialogProduct" :product="dialogProduct"

View File

@@ -12,7 +12,7 @@ defineProps({
<template> <template>
<v-card link> <v-card link>
<v-img <v-img
:src="'http://127.0.0.1:3000/static/' + product.imageUrl" :src="'http://127.0.0.1:3000/static/' + product.images[0]"
cover cover
max-height="200" max-height="200"
class="align-end text-white" class="align-end text-white"
@@ -55,7 +55,6 @@ defineProps({
<div class="ml-4 mb-1 bg-red">-{{ product.discount }} %</div> <div class="ml-4 mb-1 bg-red">-{{ product.discount }} %</div>
</div> </div>
</div> </div>
</v-card-text> </v-card-text>
</v-card> </v-card>
</template> </template>

View File

@@ -1,19 +1,23 @@
<script setup lang="ts"> <script setup lang="ts">
import { VNumberInput } from 'vuetify/labs/VNumberInput' import { VNumberInput } from 'vuetify/labs/VNumberInput'
import { ModelRef, ref } from 'vue'; import { ModelRef, ref, watch } from 'vue';
import { useBasketStore } from '@/data/stores/basketStore'; import { useBasketStore } from '@/data/stores/basketStore';
import { calcProductPrice, productToBasketItem } from '@/scripts/productScripts'; import { calcProductPrice, productToBasketItem } from '@/scripts/productScripts';
import ActionDialog from '@/components/actionDialog.vue' import ActionDialog from '@/components/actionDialog.vue'
import { ProductWithCategoryModel } from '@/data/models/productWithCategoryModel'; import { ProductWithCategoryModel } from '@/data/models/productWithCategoryModel';
import { BasketItemModel } from '@/data/models/basketItemModel'; import { BasketItemModel } from '@/data/models/basketItemModel';
const props = defineProps({
product: {
type: ProductWithCategoryModel,
default: new ProductWithCategoryModel()
}
})
const showDialog: ModelRef<boolean> = defineModel() const showDialog: ModelRef<boolean> = defineModel()
const nrOfArticles = ref(1) const nrOfArticles = ref(1)
const basketStore = useBasketStore() const basketStore = useBasketStore()
const selectedImage = ref("")
const props = defineProps({
product: ProductWithCategoryModel
})
function addProductToBasket() { function addProductToBasket() {
basketStore.addItemToBasket( basketStore.addItemToBasket(
@@ -23,23 +27,43 @@ function addProductToBasket() {
nrOfArticles.value = 1 nrOfArticles.value = 1
showDialog.value = false showDialog.value = false
} }
watch(() => props.product.images, () => {
selectedImage.value = 'http://localhost:3000/static/' + props.product.images[0]
})
</script> </script>
<template> <template>
<action-dialog <action-dialog
:title="product.name" :title="product.brand + ': ' + product.name"
:subtitle="product.brand" :icon="product.category.icon"
:image-url="'http://127.0.0.1:3000/static/' + product.imageUrl" :subtitle="product.category.name"
v-model="showDialog" v-model="showDialog"
> >
<template #content> <template #content>
<v-container>
<v-row>
<!-- Image col -->
<v-col>
<v-row> <v-row>
<v-col> <v-col>
<v-icon :icon="product.category.icon" /> <v-img :src="selectedImage" max-height="600" />
{{ product.category.name }}
</v-col> </v-col>
</v-row> </v-row>
<v-row>
<v-col v-for="image in product.images">
<v-card width="60" @click="selectedImage = 'http://localhost:3000/static/' + image" >
<v-img :src="'http://localhost:3000/static/' + image" max-height="60" />
</v-card>
</v-col>
</v-row>
</v-col>
<!-- Product description col -->
<v-col>
<v-row> <v-row>
<v-col class="text-h6"> <v-col class="text-h6">
{{ $t("product.description") }} {{ $t("product.description") }}
@@ -74,6 +98,7 @@ function addProductToBasket() {
:hideInput="false" :hideInput="false"
:inset="false" :inset="false"
v-model="nrOfArticles" v-model="nrOfArticles"
variant="outlined"
:min="1" :min="1"
:max="10" :max="10"
density="comfortable" density="comfortable"
@@ -82,10 +107,21 @@ function addProductToBasket() {
</v-row> </v-row>
<v-row> <v-row>
<v-col class="d-flex align-center flex-column my-auto text-h3"> <v-col class="d-flex align-end flex-column my-auto">
{{ calcProductPrice(product, nrOfArticles) }} <div v-if="product.discount == 0" class="text-h3">{{ product.price }} </div>
<div v-else class="d-flex align-center flex-column my-auto">
<div class="text-h3">
<span class="text-red-lighten-1"> {{ calcProductPrice(product, nrOfArticles) }} </span>
<span class="text-h6 ml-2 text-decoration-line-through">{{ product.price }} </span>
<span class="text-h6 ml-4 mb-1 bg-red">-{{ product.discount }} %</span>
</div>
</div>
</v-col> </v-col>
</v-row> </v-row>
</v-col>
</v-row>
</v-container>
</template> </template>
<template #actions> <template #actions>