2023-02-28 08:29:30 +00:00
|
|
|
<template>
|
|
|
|
<DocSectionText v-bind="$attrs">
|
2024-08-29 21:43:40 +00:00
|
|
|
<p>Place the Popover outside of the data iteration components to avoid rendering it multiple times.</p>
|
2023-02-28 08:29:30 +00:00
|
|
|
</DocSectionText>
|
|
|
|
|
2024-08-29 21:43:40 +00:00
|
|
|
<div class="card">
|
|
|
|
<DataTable :value="products" :rows="5" paginator tableStyle="min-width: 50rem">
|
|
|
|
<Column field="id" header="Id" class="w-1/6"></Column>
|
|
|
|
<Column field="code" header="Code" class="w-1/6"></Column>
|
|
|
|
<Column field="name" header="Name" class="w-1/6" bodyClass="whitespace-nowrap"></Column>
|
|
|
|
<Column field="price" header="Price" sortable class="w-1/6">
|
|
|
|
<template #body="slotProps"> $ {{ slotProps.data.price }} </template>
|
|
|
|
</Column>
|
|
|
|
<Column header="Image" class="w-1/6">
|
|
|
|
<template #body="slotProps">
|
|
|
|
<img :src="`https://primefaces.org/cdn/primevue/images/product/${slotProps.data.image}`" :alt="slotProps.data.image" class="w-16 shadow-sm" />
|
|
|
|
</template>
|
|
|
|
</Column>
|
|
|
|
<Column header="Details" class="w-1/6">
|
|
|
|
<template #body="slotProps">
|
|
|
|
<Button type="button" @click="displayProduct($event, slotProps.data)" icon="pi pi-search" severity="secondary" rounded></Button>
|
|
|
|
</template>
|
|
|
|
</Column>
|
|
|
|
</DataTable>
|
2023-02-28 08:29:30 +00:00
|
|
|
|
2024-08-29 21:43:40 +00:00
|
|
|
<Popover ref="op">
|
|
|
|
<div v-if="selectedProduct" class="rounded flex flex-col">
|
|
|
|
<div class="flex justify-center rounded">
|
|
|
|
<div class="relative mx-auto">
|
|
|
|
<img class="rounded w-44 sm:w-64" :src="`https://primefaces.org/cdn/primevue/images/product/${selectedProduct.image}`" :alt="selectedProduct.name" />
|
|
|
|
<Tag :value="selectedProduct.inventoryStatus" :severity="getSeverity(selectedProduct)" class="absolute dark:!bg-surface-900" style="left: 4px; top: 4px"></Tag>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="pt-4">
|
|
|
|
<div class="flex flex-row justify-between items-start gap-2 mb-4">
|
|
|
|
<div>
|
|
|
|
<span class="font-medium text-surface-500 dark:text-surface-400 text-sm">{{ selectedProduct.category }}</span>
|
|
|
|
<div class="text-lg font-medium mt-1">{{ selectedProduct.name }}</div>
|
|
|
|
</div>
|
|
|
|
<div class="bg-surface-100 p-1" style="border-radius: 30px">
|
|
|
|
<div class="bg-surface-0 flex items-center gap-2 justify-center py-1 px-2" style="border-radius: 30px; box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.04), 0px 1px 2px 0px rgba(0, 0, 0, 0.06)">
|
|
|
|
<span class="text-surface-900 font-medium text-sm">{{ selectedProduct.rating }}</span>
|
|
|
|
<i class="pi pi-star-fill text-yellow-500"></i>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="flex gap-2">
|
|
|
|
<Button icon="pi pi-shopping-cart" :label="`Buy Now | \$${selectedProduct.price}`" :disabled="selectedProduct.inventoryStatus === 'OUTOFSTOCK'" class="flex-auto whitespace-nowrap" @click="hidePopover"></Button>
|
|
|
|
<Button icon="pi pi-heart" outlined @click="hidePopover"></Button>
|
|
|
|
</div>
|
|
|
|
</div>
|
2023-02-28 08:29:30 +00:00
|
|
|
</div>
|
2024-04-18 14:22:30 +00:00
|
|
|
</Popover>
|
2023-02-28 08:29:30 +00:00
|
|
|
</div>
|
|
|
|
<DocSectionCode :code="code" :service="['ProductService']" />
|
|
|
|
</template>
|
|
|
|
<script>
|
|
|
|
import { ProductService } from '@/service/ProductService';
|
|
|
|
|
|
|
|
export default {
|
|
|
|
data() {
|
|
|
|
return {
|
|
|
|
products: null,
|
|
|
|
selectedProduct: null,
|
|
|
|
code: {
|
2023-09-22 12:54:14 +00:00
|
|
|
basic: `
|
2024-08-29 21:43:40 +00:00
|
|
|
<DataTable :value="products" :rows="5" paginator tableStyle="min-width: 50rem">
|
|
|
|
<Column field="id" header="Id" class="w-1/6"></Column>
|
|
|
|
<Column field="code" header="Code" class="w-1/6"></Column>
|
|
|
|
<Column field="name" header="Name" class="w-1/6" bodyClass="whitespace-nowrap"></Column>
|
|
|
|
<Column field="price" header="Price" sortable class="w-1/6">
|
|
|
|
<template #body="slotProps"> $ {{ slotProps.data.price }} </template>
|
|
|
|
</Column>
|
|
|
|
<Column header="Image" class="w-1/6">
|
|
|
|
<template #body="slotProps">
|
|
|
|
<img :src="\`https://primefaces.org/cdn/primevue/images/product/\${slotProps.data.image}\`" :alt="slotProps.data.image" class="w-16 shadow-sm" />
|
|
|
|
</template>
|
|
|
|
</Column>
|
|
|
|
<Column header="Details" class="w-1/6">
|
|
|
|
<template #body="slotProps">
|
|
|
|
<Button type="button" @click="displayProduct($event, slotProps.data)" icon="pi pi-search" severity="secondary" rounded></Button>
|
|
|
|
</template>
|
|
|
|
</Column>
|
|
|
|
</DataTable>
|
2023-02-28 08:29:30 +00:00
|
|
|
|
2024-08-29 21:43:40 +00:00
|
|
|
<Popover ref="op">
|
|
|
|
<div v-if="selectedProduct" class="rounded flex flex-col">
|
|
|
|
<div class="flex justify-center rounded">
|
|
|
|
<div class="relative mx-auto">
|
|
|
|
<img class="rounded w-44 sm:w-64" :src="\`https://primefaces.org/cdn/primevue/images/product/\${selectedProduct.image}\`" :alt="selectedProduct.name" />
|
|
|
|
<Tag :value="selectedProduct.inventoryStatus" :severity="getSeverity(selectedProduct)" class="absolute dark:!bg-surface-900" style="left: 4px; top: 4px"></Tag>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="pt-4">
|
|
|
|
<div class="flex flex-row justify-between items-start gap-2 mb-4">
|
|
|
|
<div>
|
|
|
|
<span class="font-medium text-surface-500 dark:text-surface-400 text-sm">{{ selectedProduct.category }}</span>
|
|
|
|
<div class="text-lg font-medium mt-1">{{ selectedProduct.name }}</div>
|
|
|
|
</div>
|
|
|
|
<div class="bg-surface-100 p-1" style="border-radius: 30px">
|
|
|
|
<div class="bg-surface-0 flex items-center gap-2 justify-center py-1 px-2" style="border-radius: 30px; box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.04), 0px 1px 2px 0px rgba(0, 0, 0, 0.06)">
|
|
|
|
<span class="text-surface-900 font-medium text-sm">{{ selectedProduct.rating }}</span>
|
|
|
|
<i class="pi pi-star-fill text-yellow-500"></i>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="flex gap-2">
|
|
|
|
<Button icon="pi pi-shopping-cart" :label="\`Buy Now | \\$\${selectedProduct.price}\`" :disabled="selectedProduct.inventoryStatus === 'OUTOFSTOCK'" class="flex-auto whitespace-nowrap" @click="hidePopover"></Button>
|
|
|
|
<Button icon="pi pi-heart" outlined @click="hidePopover"></Button>
|
|
|
|
</div>
|
|
|
|
</div>
|
2023-02-28 08:29:30 +00:00
|
|
|
</div>
|
2024-04-18 14:22:30 +00:00
|
|
|
</Popover>
|
2023-10-15 09:38:39 +00:00
|
|
|
`,
|
2023-09-22 12:54:14 +00:00
|
|
|
options: `
|
|
|
|
<template>
|
2024-08-29 21:43:40 +00:00
|
|
|
<div class="card">
|
|
|
|
<DataTable :value="products" :rows="5" paginator tableStyle="min-width: 50rem">
|
|
|
|
<Column field="id" header="Id" class="w-1/6"></Column>
|
|
|
|
<Column field="code" header="Code" class="w-1/6"></Column>
|
|
|
|
<Column field="name" header="Name" class="w-1/6" bodyClass="whitespace-nowrap"></Column>
|
|
|
|
<Column field="price" header="Price" sortable class="w-1/6">
|
|
|
|
<template #body="slotProps"> $ {{ slotProps.data.price }} </template>
|
|
|
|
</Column>
|
|
|
|
<Column header="Image" class="w-1/6">
|
|
|
|
<template #body="slotProps">
|
|
|
|
<img :src="\`https://primefaces.org/cdn/primevue/images/product/\${slotProps.data.image}\`" :alt="slotProps.data.image" class="w-16 shadow-sm" />
|
|
|
|
</template>
|
|
|
|
</Column>
|
|
|
|
<Column header="Details" class="w-1/6">
|
|
|
|
<template #body="slotProps">
|
|
|
|
<Button type="button" @click="displayProduct($event, slotProps.data)" icon="pi pi-search" severity="secondary" rounded></Button>
|
|
|
|
</template>
|
|
|
|
</Column>
|
|
|
|
</DataTable>
|
2023-02-28 08:29:30 +00:00
|
|
|
|
2024-08-29 21:43:40 +00:00
|
|
|
<Popover ref="op">
|
|
|
|
<div v-if="selectedProduct" class="rounded flex flex-col">
|
|
|
|
<div class="flex justify-center rounded">
|
|
|
|
<div class="relative mx-auto">
|
|
|
|
<img class="rounded w-44 sm:w-64" :src="\`https://primefaces.org/cdn/primevue/images/product/\${selectedProduct.image}\`" :alt="selectedProduct.name" />
|
|
|
|
<Tag :value="selectedProduct.inventoryStatus" :severity="getSeverity(selectedProduct)" class="absolute dark:!bg-surface-900" style="left: 4px; top: 4px"></Tag>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="pt-4">
|
|
|
|
<div class="flex flex-row justify-between items-start gap-2 mb-4">
|
|
|
|
<div>
|
|
|
|
<span class="font-medium text-surface-500 dark:text-surface-400 text-sm">{{ selectedProduct.category }}</span>
|
|
|
|
<div class="text-lg font-medium mt-1">{{ selectedProduct.name }}</div>
|
|
|
|
</div>
|
|
|
|
<div class="bg-surface-100 p-1" style="border-radius: 30px">
|
|
|
|
<div class="bg-surface-0 flex items-center gap-2 justify-center py-1 px-2" style="border-radius: 30px; box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.04), 0px 1px 2px 0px rgba(0, 0, 0, 0.06)">
|
|
|
|
<span class="text-surface-900 font-medium text-sm">{{ selectedProduct.rating }}</span>
|
|
|
|
<i class="pi pi-star-fill text-yellow-500"></i>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="flex gap-2">
|
|
|
|
<Button icon="pi pi-shopping-cart" :label="\`Buy Now | \\$\${selectedProduct.price}\`" :disabled="selectedProduct.inventoryStatus === 'OUTOFSTOCK'" class="flex-auto whitespace-nowrap" @click="hidePopover"></Button>
|
|
|
|
<Button icon="pi pi-heart" outlined @click="hidePopover"></Button>
|
|
|
|
</div>
|
|
|
|
</div>
|
2023-02-28 08:29:30 +00:00
|
|
|
</div>
|
2024-04-18 14:22:30 +00:00
|
|
|
</Popover>
|
2023-02-28 08:29:30 +00:00
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<script>
|
|
|
|
import { ProductService } from '@/service/ProductService';
|
|
|
|
|
|
|
|
export default {
|
|
|
|
data() {
|
|
|
|
return {
|
|
|
|
products: null,
|
|
|
|
selectedProduct: null
|
|
|
|
};
|
|
|
|
},
|
|
|
|
mounted() {
|
2024-08-29 21:43:40 +00:00
|
|
|
ProductService.getProductsSmall().then((data) => (this.products = data));
|
2023-02-28 08:29:30 +00:00
|
|
|
},
|
|
|
|
methods: {
|
2024-08-29 21:43:40 +00:00
|
|
|
displayProduct(event, product) {
|
2024-09-09 17:22:20 +00:00
|
|
|
this.$refs.op.hide();
|
|
|
|
|
2024-08-29 21:43:40 +00:00
|
|
|
if (this.selectedProduct?.id === product.id) {
|
|
|
|
this.selectedProduct = null;
|
|
|
|
} else {
|
|
|
|
this.selectedProduct = product;
|
|
|
|
|
2024-09-09 17:22:20 +00:00
|
|
|
this.$nextTick(() => {
|
|
|
|
this.$refs.op.show(event);
|
|
|
|
});
|
2024-08-29 21:43:40 +00:00
|
|
|
}
|
2023-02-28 08:29:30 +00:00
|
|
|
},
|
2024-08-29 21:43:40 +00:00
|
|
|
hidePopover() {
|
2023-02-28 08:29:30 +00:00
|
|
|
this.$refs.op.hide();
|
2024-08-29 21:43:40 +00:00
|
|
|
},
|
|
|
|
getSeverity(product) {
|
|
|
|
switch (product.inventoryStatus) {
|
|
|
|
case 'INSTOCK':
|
|
|
|
return 'success';
|
|
|
|
|
|
|
|
case 'LOWSTOCK':
|
|
|
|
return 'warn';
|
|
|
|
|
|
|
|
case 'OUTOFSTOCK':
|
|
|
|
return 'danger';
|
|
|
|
|
|
|
|
default:
|
|
|
|
return null;
|
|
|
|
}
|
2023-02-28 08:29:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
2023-10-15 09:38:39 +00:00
|
|
|
<\/script>
|
|
|
|
`,
|
2023-09-22 12:54:14 +00:00
|
|
|
composition: `
|
|
|
|
<template>
|
2024-08-29 21:43:40 +00:00
|
|
|
<div class="card">
|
|
|
|
<DataTable :value="products" :rows="5" paginator tableStyle="min-width: 50rem">
|
|
|
|
<Column field="id" header="Id" class="w-1/6"></Column>
|
|
|
|
<Column field="code" header="Code" class="w-1/6"></Column>
|
|
|
|
<Column field="name" header="Name" class="w-1/6" bodyClass="whitespace-nowrap"></Column>
|
|
|
|
<Column field="price" header="Price" sortable class="w-1/6">
|
|
|
|
<template #body="slotProps"> $ {{ slotProps.data.price }} </template>
|
|
|
|
</Column>
|
|
|
|
<Column header="Image" class="w-1/6">
|
|
|
|
<template #body="slotProps">
|
|
|
|
<img :src="\`https://primefaces.org/cdn/primevue/images/product/\${slotProps.data.image}\`" :alt="slotProps.data.image" class="w-16 shadow-sm" />
|
|
|
|
</template>
|
|
|
|
</Column>
|
|
|
|
<Column header="Details" class="w-1/6">
|
|
|
|
<template #body="slotProps">
|
|
|
|
<Button type="button" @click="displayProduct($event, slotProps.data)" icon="pi pi-search" severity="secondary" rounded></Button>
|
|
|
|
</template>
|
|
|
|
</Column>
|
|
|
|
</DataTable>
|
2023-02-28 08:29:30 +00:00
|
|
|
|
2024-08-29 21:43:40 +00:00
|
|
|
<Popover ref="op">
|
|
|
|
<div v-if="selectedProduct" class="rounded flex flex-col">
|
|
|
|
<div class="flex justify-center rounded">
|
|
|
|
<div class="relative mx-auto">
|
|
|
|
<img class="rounded w-44 sm:w-64" :src="\`https://primefaces.org/cdn/primevue/images/product/\${selectedProduct.image}\`" :alt="selectedProduct.name" />
|
|
|
|
<Tag :value="selectedProduct.inventoryStatus" :severity="getSeverity(selectedProduct)" class="absolute dark:!bg-surface-900" style="left: 4px; top: 4px"></Tag>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="pt-4">
|
|
|
|
<div class="flex flex-row justify-between items-start gap-2 mb-4">
|
|
|
|
<div>
|
|
|
|
<span class="font-medium text-surface-500 dark:text-surface-400 text-sm">{{ selectedProduct.category }}</span>
|
|
|
|
<div class="text-lg font-medium mt-1">{{ selectedProduct.name }}</div>
|
|
|
|
</div>
|
|
|
|
<div class="bg-surface-100 p-1" style="border-radius: 30px">
|
|
|
|
<div class="bg-surface-0 flex items-center gap-2 justify-center py-1 px-2" style="border-radius: 30px; box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.04), 0px 1px 2px 0px rgba(0, 0, 0, 0.06)">
|
|
|
|
<span class="text-surface-900 font-medium text-sm">{{ selectedProduct.rating }}</span>
|
|
|
|
<i class="pi pi-star-fill text-yellow-500"></i>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="flex gap-2">
|
|
|
|
<Button icon="pi pi-shopping-cart" :label="\`Buy Now | \\$\${selectedProduct.price}\`" :disabled="selectedProduct.inventoryStatus === 'OUTOFSTOCK'" class="flex-auto whitespace-nowrap" @click="hidePopover"></Button>
|
|
|
|
<Button icon="pi pi-heart" outlined @click="hidePopover"></Button>
|
|
|
|
</div>
|
|
|
|
</div>
|
2023-02-28 08:29:30 +00:00
|
|
|
</div>
|
2024-04-18 14:22:30 +00:00
|
|
|
</Popover>
|
2023-02-28 08:29:30 +00:00
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<script setup>
|
2024-09-09 17:22:20 +00:00
|
|
|
import { ref, onMounted, nextTick } from "vue";
|
2023-02-28 08:29:30 +00:00
|
|
|
import { useToast } from "primevue/usetoast";
|
|
|
|
import { ProductService } from '@/service/ProductService';
|
|
|
|
|
|
|
|
onMounted(() => {
|
2024-08-29 21:43:40 +00:00
|
|
|
ProductService.getProductsSmall().then((data) => (products.value = data));
|
2023-02-28 08:29:30 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
const op = ref();
|
|
|
|
const products = ref();
|
|
|
|
const selectedProduct = ref();
|
|
|
|
|
2024-08-29 21:43:40 +00:00
|
|
|
const displayProduct = (event, product) => {
|
2024-09-09 17:22:20 +00:00
|
|
|
op.value.hide();
|
|
|
|
|
2024-08-29 21:43:40 +00:00
|
|
|
if (selectedProduct.value?.id === product.id) {
|
|
|
|
selectedProduct.value = null;
|
|
|
|
} else {
|
|
|
|
selectedProduct.value = product;
|
|
|
|
|
2024-09-09 17:22:20 +00:00
|
|
|
nextTick(() => {
|
|
|
|
op.value.show(event);
|
|
|
|
});
|
2024-08-29 21:43:40 +00:00
|
|
|
}
|
|
|
|
}
|
2023-02-28 08:29:30 +00:00
|
|
|
|
2024-08-29 21:43:40 +00:00
|
|
|
const hidePopover = () => {
|
2023-02-28 08:29:30 +00:00
|
|
|
op.value.hide();
|
2024-08-29 21:43:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const getSeverity = (product) => {
|
|
|
|
switch (product.inventoryStatus) {
|
|
|
|
case 'INSTOCK':
|
|
|
|
return 'success';
|
|
|
|
|
|
|
|
case 'LOWSTOCK':
|
|
|
|
return 'warn';
|
|
|
|
|
|
|
|
case 'OUTOFSTOCK':
|
|
|
|
return 'danger';
|
|
|
|
|
|
|
|
default:
|
|
|
|
return null;
|
|
|
|
}
|
2023-02-28 08:29:30 +00:00
|
|
|
}
|
2023-10-15 09:38:39 +00:00
|
|
|
<\/script>
|
|
|
|
`,
|
2023-02-28 08:29:30 +00:00
|
|
|
data: `
|
|
|
|
/* ProductService */
|
|
|
|
{
|
|
|
|
id: '1000',
|
|
|
|
code: 'f230fh0g3',
|
|
|
|
name: 'Bamboo Watch',
|
|
|
|
description: 'Product Description',
|
|
|
|
image: '/bamboo-watch.jpg',
|
|
|
|
price: 65,
|
|
|
|
category: 'Accessories',
|
|
|
|
quantity: 24,
|
|
|
|
inventoryStatus: 'INSTOCK',
|
|
|
|
rating: 5
|
|
|
|
},
|
|
|
|
...
|
|
|
|
`
|
|
|
|
}
|
|
|
|
};
|
|
|
|
},
|
|
|
|
mounted() {
|
2024-08-29 21:43:40 +00:00
|
|
|
ProductService.getProductsSmall().then((data) => (this.products = data));
|
2023-02-28 08:29:30 +00:00
|
|
|
},
|
|
|
|
methods: {
|
2024-08-29 21:43:40 +00:00
|
|
|
displayProduct(event, product) {
|
2024-09-09 17:22:20 +00:00
|
|
|
this.$refs.op.hide();
|
|
|
|
|
2024-08-29 21:43:40 +00:00
|
|
|
if (this.selectedProduct?.id === product.id) {
|
|
|
|
this.selectedProduct = null;
|
|
|
|
} else {
|
|
|
|
this.selectedProduct = product;
|
|
|
|
|
2024-09-09 17:22:20 +00:00
|
|
|
this.$nextTick(() => {
|
|
|
|
this.$refs.op.show(event);
|
|
|
|
});
|
2024-08-29 21:43:40 +00:00
|
|
|
}
|
2023-02-28 08:29:30 +00:00
|
|
|
},
|
2024-08-29 21:43:40 +00:00
|
|
|
hidePopover() {
|
2023-02-28 08:29:30 +00:00
|
|
|
this.$refs.op.hide();
|
2024-08-29 21:43:40 +00:00
|
|
|
},
|
|
|
|
getSeverity(product) {
|
|
|
|
switch (product.inventoryStatus) {
|
|
|
|
case 'INSTOCK':
|
|
|
|
return 'success';
|
|
|
|
|
|
|
|
case 'LOWSTOCK':
|
|
|
|
return 'warn';
|
|
|
|
|
|
|
|
case 'OUTOFSTOCK':
|
|
|
|
return 'danger';
|
|
|
|
|
|
|
|
default:
|
|
|
|
return null;
|
|
|
|
}
|
2023-02-28 08:29:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
</script>
|