<template> <DocSectionText v-bind="$attrs"> <p>CRUD implementation example with a Dialog.</p> </DocSectionText> <div class="card"> <Toolbar class="mb-4"> <template #start> <Button label="New" icon="pi pi-plus" severity="success" class="mr-2" @click="openNew" /> <Button label="Delete" icon="pi pi-trash" severity="danger" @click="confirmDeleteSelected" :disabled="!selectedProducts || !selectedProducts.length" /> </template> <template #end> <FileUpload mode="basic" accept="image/*" :maxFileSize="1000000" label="Import" chooseLabel="Import" class="mr-2 inline-block" /> <Button label="Export" icon="pi pi-upload" severity="help" @click="exportCSV($event)" /> </template> </Toolbar> <DataTable ref="dt" v-model:selection="selectedProducts" :value="products" dataKey="id" :paginator="true" :rows="10" :filters="filters" paginatorTemplate="FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink CurrentPageReport RowsPerPageDropdown" :rowsPerPageOptions="[5, 10, 25]" currentPageReportTemplate="Showing {first} to {last} of {totalRecords} products" > <template #header> <div class="flex flex-wrap gap-2 align-items-center justify-content-between"> <h4 class="m-0">Manage Products</h4> <span class="p-input-icon-left"> <i class="pi pi-search" /> <InputText v-model="filters['global'].value" placeholder="Search..." /> </span> </div> </template> <Column selectionMode="multiple" style="width: 3rem" :exportable="false"></Column> <Column field="code" header="Code" sortable style="min-width: 12rem"></Column> <Column field="name" header="Name" sortable style="min-width: 16rem"></Column> <Column header="Image"> <template #body="slotProps"> <img :src="`https://primefaces.org/cdn/primevue/images/product/${slotProps.data.image}`" :alt="slotProps.data.image" class="shadow-2 border-round" style="width: 64px" /> </template> </Column> <Column field="price" header="Price" sortable style="min-width: 8rem"> <template #body="slotProps"> {{ formatCurrency(slotProps.data.price) }} </template> </Column> <Column field="category" header="Category" sortable style="min-width: 10rem"></Column> <Column field="rating" header="Reviews" sortable style="min-width: 12rem"> <template #body="slotProps"> <Rating :modelValue="slotProps.data.rating" :readonly="true" :cancel="false" /> </template> </Column> <Column field="inventoryStatus" header="Status" sortable style="min-width: 12rem"> <template #body="slotProps"> <Tag :value="slotProps.data.inventoryStatus" :severity="getStatusLabel(slotProps.data.inventoryStatus)" /> </template> </Column> <Column :exportable="false" style="min-width: 12rem"> <template #body="slotProps"> <Button icon="pi pi-pencil" outlined rounded class="mr-2" @click="editProduct(slotProps.data)" /> <Button icon="pi pi-trash" outlined rounded severity="danger" @click="confirmDeleteProduct(slotProps.data)" /> </template> </Column> </DataTable> </div> <Dialog v-model:visible="productDialog" :style="{ width: '450px' }" header="Product Details" :modal="true" class="p-fluid"> <img v-if="product.image" :src="`https://primefaces.org/cdn/primevue/images/product/${product.image}`" :alt="product.image" class="block m-auto pb-3" /> <div class="field"> <label for="name">Name</label> <InputText id="name" v-model.trim="product.name" required="true" autofocus :class="{ 'p-invalid': submitted && !product.name }" /> <small v-if="submitted && !product.name" class="p-error">Name is required.</small> </div> <div class="field"> <label for="description">Description</label> <Textarea id="description" v-model="product.description" required="true" rows="3" cols="20" /> </div> <div class="field"> <label for="inventoryStatus" class="mb-3">Inventory Status</label> <Dropdown id="inventoryStatus" v-model="product.inventoryStatus" :options="statuses" optionLabel="label" placeholder="Select a Status"> <template #value="slotProps"> <div v-if="slotProps.value && slotProps.value.value"> <Tag :value="slotProps.value.value" :severity="getStatusLabel(slotProps.value.label)" /> </div> <div v-else-if="slotProps.value && !slotProps.value.value"> <Tag :value="slotProps.value" :severity="getStatusLabel(slotProps.value)" /> </div> <span v-else> {{ slotProps.placeholder }} </span> </template> </Dropdown> </div> <div class="field"> <label class="mb-3">Category</label> <div class="formgrid grid"> <div class="field-radiobutton col-6"> <RadioButton id="category1" v-model="product.category" name="category" value="Accessories" /> <label for="category1">Accessories</label> </div> <div class="field-radiobutton col-6"> <RadioButton id="category2" v-model="product.category" name="category" value="Clothing" /> <label for="category2">Clothing</label> </div> <div class="field-radiobutton col-6"> <RadioButton id="category3" v-model="product.category" name="category" value="Electronics" /> <label for="category3">Electronics</label> </div> <div class="field-radiobutton col-6"> <RadioButton id="category4" v-model="product.category" name="category" value="Fitness" /> <label for="category4">Fitness</label> </div> </div> </div> <div class="formgrid grid"> <div class="field col"> <label for="price">Price</label> <InputNumber id="price" v-model="product.price" mode="currency" currency="USD" locale="en-US" /> </div> <div class="field col"> <label for="quantity">Quantity</label> <InputNumber id="quantity" v-model="product.quantity" integeronly /> </div> </div> <template #footer> <Button label="Cancel" icon="pi pi-times" text @click="hideDialog" /> <Button label="Save" icon="pi pi-check" text @click="saveProduct" /> </template> </Dialog> <Dialog v-model:visible="deleteProductDialog" :style="{ width: '450px' }" header="Confirm" :modal="true"> <div class="confirmation-content"> <i class="pi pi-exclamation-triangle mr-3" style="font-size: 2rem" /> <span v-if="product" >Are you sure you want to delete <b>{{ product.name }}</b >?</span > </div> <template #footer> <Button label="No" icon="pi pi-times" text @click="deleteProductDialog = false" /> <Button label="Yes" icon="pi pi-check" text @click="deleteProduct" /> </template> </Dialog> <Dialog v-model:visible="deleteProductsDialog" :style="{ width: '450px' }" header="Confirm" :modal="true"> <div class="confirmation-content"> <i class="pi pi-exclamation-triangle mr-3" style="font-size: 2rem" /> <span v-if="product">Are you sure you want to delete the selected products?</span> </div> <template #footer> <Button label="No" icon="pi pi-times" text @click="deleteProductsDialog = false" /> <Button label="Yes" icon="pi pi-check" text @click="deleteSelectedProducts" /> </template> </Dialog> <DocSectionCode :code="code" :service="['ProductService']" /> </template> <script> import { ProductService } from '@/service/ProductService'; import { FilterMatchMode } from 'primevue/api'; export default { data() { return { products: null, productDialog: false, deleteProductDialog: false, deleteProductsDialog: false, product: {}, selectedProducts: null, filters: {}, submitted: false, statuses: [ { label: 'INSTOCK', value: 'instock' }, { label: 'LOWSTOCK', value: 'lowstock' }, { label: 'OUTOFSTOCK', value: 'outofstock' } ], code: { basic: ` <Toolbar class="mb-4"> <template #start> <Button label="New" icon="pi pi-plus" severity="success" class="mr-2" @click="openNew" /> <Button label="Delete" icon="pi pi-trash" severity="danger" @click="confirmDeleteSelected" :disabled="!selectedProducts || !selectedProducts.length" /> </template> <template #end> <FileUpload mode="basic" accept="image/*" :maxFileSize="1000000" label="Import" chooseLabel="Import" class="mr-2 inline-block" /> <Button label="Export" icon="pi pi-upload" severity="help" @click="exportCSV($event)" /> </template> </Toolbar> <DataTable ref="dt" :value="products" v-model:selection="selectedProducts" dataKey="id" :paginator="true" :rows="10" :filters="filters" paginatorTemplate="FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink CurrentPageReport RowsPerPageDropdown" :rowsPerPageOptions="[5,10,25]" currentPageReportTemplate="Showing {first} to {last} of {totalRecords} products"> <template #header> <div class="flex flex-wrap gap-2 align-items-center justify-content-between"> <h4 class="m-0">Manage Products</h4> <span class="p-input-icon-left"> <i class="pi pi-search" /> <InputText v-model="filters['global'].value" placeholder="Search..." /> </span> </div> </template> <Column selectionMode="multiple" style="width: 3rem" :exportable="false"></Column> <Column field="code" header="Code" sortable style="min-width:12rem"></Column> <Column field="name" header="Name" sortable style="min-width:16rem"></Column> <Column header="Image"> <template #body="slotProps"> <img :src="\`https://primefaces.org/cdn/primevue/images/product/\${slotProps.data.image}\`" :alt="slotProps.data.image" class="shadow-2 border-round" style="width: 64px" /> </template> </Column> <Column field="price" header="Price" sortable style="min-width:8rem"> <template #body="slotProps"> {{formatCurrency(slotProps.data.price)}} </template> </Column> <Column field="category" header="Category" sortable style="min-width:10rem"></Column> <Column field="rating" header="Reviews" sortable style="min-width:12rem"> <template #body="slotProps"> <Rating :modelValue="slotProps.data.rating" :readonly="true" :cancel="false" /> </template> </Column> <Column field="inventoryStatus" header="Status" sortable style="min-width:12rem"> <template #body="slotProps"> <Tag :value="slotProps.data.inventoryStatus" :severity="getStatusLabel(slotProps.data.inventoryStatus)" /> </template> </Column> <Column :exportable="false" style="min-width:8rem"> <template #body="slotProps"> <Button icon="pi pi-pencil" outlined rounded class="mr-2" @click="editProduct(slotProps.data)" /> <Button icon="pi pi-trash" outlined rounded severity="danger" @click="confirmDeleteProduct(slotProps.data)" /> </template> </Column> </DataTable> `, options: ` <template> <div> <div class="card"> <Toolbar class="mb-4"> <template #start> <Button label="New" icon="pi pi-plus" severity="success" class="mr-2" @click="openNew" /> <Button label="Delete" icon="pi pi-trash" severity="danger" @click="confirmDeleteSelected" :disabled="!selectedProducts || !selectedProducts.length" /> </template> <template #end> <FileUpload mode="basic" accept="image/*" :maxFileSize="1000000" label="Import" chooseLabel="Import" class="mr-2 inline-block" /> <Button label="Export" icon="pi pi-upload" severity="help" @click="exportCSV($event)" /> </template> </Toolbar> <DataTable ref="dt" :value="products" v-model:selection="selectedProducts" dataKey="id" :paginator="true" :rows="10" :filters="filters" paginatorTemplate="FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink CurrentPageReport RowsPerPageDropdown" :rowsPerPageOptions="[5,10,25]" currentPageReportTemplate="Showing {first} to {last} of {totalRecords} products"> <template #header> <div class="flex flex-wrap gap-2 align-items-center justify-content-between"> <h4 class="m-0">Manage Products</h4> <span class="p-input-icon-left"> <i class="pi pi-search" /> <InputText v-model="filters['global'].value" placeholder="Search..." /> </span> </div> </template> <Column selectionMode="multiple" style="width: 3rem" :exportable="false"></Column> <Column field="code" header="Code" sortable style="min-width:12rem"></Column> <Column field="name" header="Name" sortable style="min-width:16rem"></Column> <Column header="Image"> <template #body="slotProps"> <img :src="\`https://primefaces.org/cdn/primevue/images/product/\${slotProps.data.image}\`" :alt="slotProps.data.image" class="shadow-2 border-round" style="width: 64px" /> </template> </Column> <Column field="price" header="Price" sortable style="min-width:8rem"> <template #body="slotProps"> {{formatCurrency(slotProps.data.price)}} </template> </Column> <Column field="category" header="Category" sortable style="min-width:10rem"></Column> <Column field="rating" header="Reviews" sortable style="min-width:12rem"> <template #body="slotProps"> <Rating :modelValue="slotProps.data.rating" :readonly="true" :cancel="false" /> </template> </Column> <Column field="inventoryStatus" header="Status" sortable style="min-width:12rem"> <template #body="slotProps"> <Tag :value="slotProps.data.inventoryStatus" :severity="getStatusLabel(slotProps.data.inventoryStatus)" /> </template> </Column> <Column :exportable="false" style="min-width:8rem"> <template #body="slotProps"> <Button icon="pi pi-pencil" outlined rounded class="mr-2" @click="editProduct(slotProps.data)" /> <Button icon="pi pi-trash" outlined rounded severity="danger" @click="confirmDeleteProduct(slotProps.data)" /> </template> </Column> </DataTable> </div> <Dialog v-model:visible="productDialog" :style="{width: '450px'}" header="Product Details" :modal="true" class="p-fluid"> <img v-if="product.image" :src="\`https://primefaces.org/cdn/primevue/images/product/\${product.image}\`" :alt="product.image" class="block m-auto pb-3" /> <div class="field"> <label for="name">Name</label> <InputText id="name" v-model.trim="product.name" required="true" autofocus :class="{'p-invalid': submitted && !product.name}" /> <small class="p-error" v-if="submitted && !product.name">Name is required.</small> </div> <div class="field"> <label for="description">Description</label> <Textarea id="description" v-model="product.description" required="true" rows="3" cols="20" /> </div> <div class="field"> <label for="inventoryStatus" class="mb-3">Inventory Status</label> <Dropdown id="inventoryStatus" v-model="product.inventoryStatus" :options="statuses" optionLabel="label" placeholder="Select a Status"> <template #value="slotProps"> <div v-if="slotProps.value && slotProps.value.value"> <Tag :value="slotProps.value.value" :severity="getStatusLabel(slotProps.value.label)" /> </div> <div v-else-if="slotProps.value && !slotProps.value.value"> <Tag :value="slotProps.value" :severity="getStatusLabel(slotProps.value)" /> </div> <span v-else> {{slotProps.placeholder}} </span> </template> </Dropdown> </div> <div class="field"> <label class="mb-3">Category</label> <div class="formgrid grid"> <div class="field-radiobutton col-6"> <RadioButton id="category1" name="category" value="Accessories" v-model="product.category" /> <label for="category1">Accessories</label> </div> <div class="field-radiobutton col-6"> <RadioButton id="category2" name="category" value="Clothing" v-model="product.category" /> <label for="category2">Clothing</label> </div> <div class="field-radiobutton col-6"> <RadioButton id="category3" name="category" value="Electronics" v-model="product.category" /> <label for="category3">Electronics</label> </div> <div class="field-radiobutton col-6"> <RadioButton id="category4" name="category" value="Fitness" v-model="product.category" /> <label for="category4">Fitness</label> </div> </div> </div> <div class="formgrid grid"> <div class="field col"> <label for="price">Price</label> <InputNumber id="price" v-model="product.price" mode="currency" currency="USD" locale="en-US" /> </div> <div class="field col"> <label for="quantity">Quantity</label> <InputNumber id="quantity" v-model="product.quantity" integeronly /> </div> </div> <template #footer> <Button label="Cancel" icon="pi pi-times" text @click="hideDialog"/> <Button label="Save" icon="pi pi-check" text @click="saveProduct" /> </template> </Dialog> <Dialog v-model:visible="deleteProductDialog" :style="{width: '450px'}" header="Confirm" :modal="true"> <div class="confirmation-content"> <i class="pi pi-exclamation-triangle mr-3" style="font-size: 2rem" /> <span v-if="product">Are you sure you want to delete <b>{{product.name}}</b>?</span> </div> <template #footer> <Button label="No" icon="pi pi-times" text @click="deleteProductDialog = false"/> <Button label="Yes" icon="pi pi-check" text @click="deleteProduct" /> </template> </Dialog> <Dialog v-model:visible="deleteProductsDialog" :style="{width: '450px'}" header="Confirm" :modal="true"> <div class="confirmation-content"> <i class="pi pi-exclamation-triangle mr-3" style="font-size: 2rem" /> <span v-if="product">Are you sure you want to delete the selected products?</span> </div> <template #footer> <Button label="No" icon="pi pi-times" text @click="deleteProductsDialog = false"/> <Button label="Yes" icon="pi pi-check" text @click="deleteSelectedProducts" /> </template> </Dialog> </div> </template> <script> import { FilterMatchMode } from 'primevue/api'; import { ProductService } from '@/service/ProductService'; export default { data() { return { products: null, productDialog: false, deleteProductDialog: false, deleteProductsDialog: false, product: {}, selectedProducts: null, filters: {}, submitted: false, statuses: [ {label: 'INSTOCK', value: 'instock'}, {label: 'LOWSTOCK', value: 'lowstock'}, {label: 'OUTOFSTOCK', value: 'outofstock'} ] } }, created() { this.initFilters(); }, mounted() { ProductService.getProducts().then((data) => (this.products = data)); }, methods: { formatCurrency(value) { if(value) return value.toLocaleString('en-US', {style: 'currency', currency: 'USD'}); return; }, openNew() { this.product = {}; this.submitted = false; this.productDialog = true; }, hideDialog() { this.productDialog = false; this.submitted = false; }, saveProduct() { this.submitted = true; if (this.product.name.trim()) { if (this.product.id) { this.product.inventoryStatus = this.product.inventoryStatus.value ? this.product.inventoryStatus.value: this.product.inventoryStatus; this.products[this.findIndexById(this.product.id)] = this.product; this.$toast.add({severity:'success', summary: 'Successful', detail: 'Product Updated', life: 3000}); } else { this.product.id = this.createId(); this.product.code = this.createId(); this.product.image = 'product-placeholder.svg'; this.product.inventoryStatus = this.product.inventoryStatus ? this.product.inventoryStatus.value : 'INSTOCK'; this.products.push(this.product); this.$toast.add({severity:'success', summary: 'Successful', detail: 'Product Created', life: 3000}); } this.productDialog = false; this.product = {}; } }, editProduct(product) { this.product = {...product}; this.productDialog = true; }, confirmDeleteProduct(product) { this.product = product; this.deleteProductDialog = true; }, deleteProduct() { this.products = this.products.filter(val => val.id !== this.product.id); this.deleteProductDialog = false; this.product = {}; this.$toast.add({severity:'success', summary: 'Successful', detail: 'Product Deleted', life: 3000}); }, findIndexById(id) { let index = -1; for (let i = 0; i < this.products.length; i++) { if (this.products[i].id === id) { index = i; break; } } return index; }, createId() { let id = ''; var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; for ( var i = 0; i < 5; i++ ) { id += chars.charAt(Math.floor(Math.random() * chars.length)); } return id; }, exportCSV() { this.$refs.dt.exportCSV(); }, confirmDeleteSelected() { this.deleteProductsDialog = true; }, deleteSelectedProducts() { this.products = this.products.filter(val => !this.selectedProducts.includes(val)); this.deleteProductsDialog = false; this.selectedProducts = null; this.$toast.add({severity:'success', summary: 'Successful', detail: 'Products Deleted', life: 3000}); }, initFilters() { this.filters = { 'global': {value: null, matchMode: FilterMatchMode.CONTAINS}, } }, getStatusLabel(status) { switch (status) { case 'INSTOCK': return 'success'; case 'LOWSTOCK': return 'warning'; case 'OUTOFSTOCK': return 'danger'; default: return null; } } } } <\/script> `, composition: ` <template> <div> <div class="card"> <Toolbar class="mb-4"> <template #start> <Button label="New" icon="pi pi-plus" severity="success" class="mr-2" @click="openNew" /> <Button label="Delete" icon="pi pi-trash" severity="danger" @click="confirmDeleteSelected" :disabled="!selectedProducts || !selectedProducts.length" /> </template> <template #end> <FileUpload mode="basic" accept="image/*" :maxFileSize="1000000" label="Import" chooseLabel="Import" class="mr-2 inline-block" /> <Button label="Export" icon="pi pi-upload" severity="help" @click="exportCSV($event)" /> </template> </Toolbar> <DataTable ref="dt" :value="products" v-model:selection="selectedProducts" dataKey="id" :paginator="true" :rows="10" :filters="filters" paginatorTemplate="FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink CurrentPageReport RowsPerPageDropdown" :rowsPerPageOptions="[5,10,25]" currentPageReportTemplate="Showing {first} to {last} of {totalRecords} products"> <template #header> <div class="flex flex-wrap gap-2 align-items-center justify-content-between"> <h4 class="m-0">Manage Products</h4> <span class="p-input-icon-left"> <i class="pi pi-search" /> <InputText v-model="filters['global'].value" placeholder="Search..." /> </span> </div> </template> <Column selectionMode="multiple" style="width: 3rem" :exportable="false"></Column> <Column field="code" header="Code" sortable style="min-width:12rem"></Column> <Column field="name" header="Name" sortable style="min-width:16rem"></Column> <Column header="Image"> <template #body="slotProps"> <img :src="\`https://primefaces.org/cdn/primevue/images/product/\${slotProps.data.image}\`" :alt="slotProps.data.image" class="shadow-2 border-round" style="width: 64px" /> </template> </Column> <Column field="price" header="Price" sortable style="min-width:8rem"> <template #body="slotProps"> {{formatCurrency(slotProps.data.price)}} </template> </Column> <Column field="category" header="Category" sortable style="min-width:10rem"></Column> <Column field="rating" header="Reviews" sortable style="min-width:12rem"> <template #body="slotProps"> <Rating :modelValue="slotProps.data.rating" :readonly="true" :cancel="false" /> </template> </Column> <Column field="inventoryStatus" header="Status" sortable style="min-width:12rem"> <template #body="slotProps"> <Tag :value="slotProps.data.inventoryStatus" :severity="getStatusLabel(slotProps.data.inventoryStatus)" /> </template> </Column> <Column :exportable="false" style="min-width:8rem"> <template #body="slotProps"> <Button icon="pi pi-pencil" outlined rounded class="mr-2" @click="editProduct(slotProps.data)" /> <Button icon="pi pi-trash" outlined rounded severity="danger" @click="confirmDeleteProduct(slotProps.data)" /> </template> </Column> </DataTable> </div> <Dialog v-model:visible="productDialog" :style="{width: '450px'}" header="Product Details" :modal="true" class="p-fluid"> <img v-if="product.image" :src="\`https://primefaces.org/cdn/primevue/images/product/\${product.image}\`" :alt="product.image" class="block m-auto pb-3" /> <div class="field"> <label for="name">Name</label> <InputText id="name" v-model.trim="product.name" required="true" autofocus :class="{'p-invalid': submitted && !product.name}" /> <small class="p-error" v-if="submitted && !product.name">Name is required.</small> </div> <div class="field"> <label for="description">Description</label> <Textarea id="description" v-model="product.description" required="true" rows="3" cols="20" /> </div> <div class="field"> <label for="inventoryStatus" class="mb-3">Inventory Status</label> <Dropdown id="inventoryStatus" v-model="product.inventoryStatus" :options="statuses" optionLabel="label" placeholder="Select a Status"> <template #value="slotProps"> <div v-if="slotProps.value && slotProps.value.value"> <Tag :value="slotProps.value.value" :severity="getStatusLabel(slotProps.value.label)" /> </div> <div v-else-if="slotProps.value && !slotProps.value.value"> <Tag :value="slotProps.value" :severity="getStatusLabel(slotProps.value)" /> </div> <span v-else> {{slotProps.placeholder}} </span> </template> </Dropdown> </div> <div class="field"> <label class="mb-3">Category</label> <div class="formgrid grid"> <div class="field-radiobutton col-6"> <RadioButton id="category1" name="category" value="Accessories" v-model="product.category" /> <label for="category1">Accessories</label> </div> <div class="field-radiobutton col-6"> <RadioButton id="category2" name="category" value="Clothing" v-model="product.category" /> <label for="category2">Clothing</label> </div> <div class="field-radiobutton col-6"> <RadioButton id="category3" name="category" value="Electronics" v-model="product.category" /> <label for="category3">Electronics</label> </div> <div class="field-radiobutton col-6"> <RadioButton id="category4" name="category" value="Fitness" v-model="product.category" /> <label for="category4">Fitness</label> </div> </div> </div> <div class="formgrid grid"> <div class="field col"> <label for="price">Price</label> <InputNumber id="price" v-model="product.price" mode="currency" currency="USD" locale="en-US" /> </div> <div class="field col"> <label for="quantity">Quantity</label> <InputNumber id="quantity" v-model="product.quantity" integeronly /> </div> </div> <template #footer> <Button label="Cancel" icon="pi pi-times" text @click="hideDialog"/> <Button label="Save" icon="pi pi-check" text @click="saveProduct" /> </template> </Dialog> <Dialog v-model:visible="deleteProductDialog" :style="{width: '450px'}" header="Confirm" :modal="true"> <div class="confirmation-content"> <i class="pi pi-exclamation-triangle mr-3" style="font-size: 2rem" /> <span v-if="product">Are you sure you want to delete <b>{{product.name}}</b>?</span> </div> <template #footer> <Button label="No" icon="pi pi-times" text @click="deleteProductDialog = false"/> <Button label="Yes" icon="pi pi-check" text @click="deleteProduct" /> </template> </Dialog> <Dialog v-model:visible="deleteProductsDialog" :style="{width: '450px'}" header="Confirm" :modal="true"> <div class="confirmation-content"> <i class="pi pi-exclamation-triangle mr-3" style="font-size: 2rem" /> <span v-if="product">Are you sure you want to delete the selected products?</span> </div> <template #footer> <Button label="No" icon="pi pi-times" text @click="deleteProductsDialog = false"/> <Button label="Yes" icon="pi pi-check" text @click="deleteSelectedProducts" /> </template> </Dialog> </div> </template> <script setup> import { ref, onMounted } from 'vue'; import { FilterMatchMode } from 'primevue/api'; import { useToast } from 'primevue/usetoast'; import { ProductService } from '@/service/ProductService'; onMounted(() => { ProductService.getProducts().then((data) => (products.value = data)); }); const toast = useToast(); const dt = ref(); const products = ref(); const productDialog = ref(false); const deleteProductDialog = ref(false); const deleteProductsDialog = ref(false); const product = ref({}); const selectedProducts = ref(); const filters = ref({ 'global': {value: null, matchMode: FilterMatchMode.CONTAINS}, }); const submitted = ref(false); const statuses = ref([ {label: 'INSTOCK', value: 'instock'}, {label: 'LOWSTOCK', value: 'lowstock'}, {label: 'OUTOFSTOCK', value: 'outofstock'} ]); const formatCurrency = (value) => { if(value) return value.toLocaleString('en-US', {style: 'currency', currency: 'USD'}); return; }; const openNew = () => { product.value = {}; submitted.value = false; productDialog.value = true; }; const hideDialog = () => { productDialog.value = false; submitted.value = false; }; const saveProduct = () => { submitted.value = true; if (product.value.name.trim()) { if (product.value.id) { product.value.inventoryStatus = product.value.inventoryStatus.value ? product.value.inventoryStatus.value : product.value.inventoryStatus; products.value[findIndexById(product.value.id)] = product.value; toast.add({severity:'success', summary: 'Successful', detail: 'Product Updated', life: 3000}); } else { product.value.id = createId(); product.value.code = createId(); product.value.image = 'product-placeholder.svg'; product.value.inventoryStatus = product.value.inventoryStatus ? product.value.inventoryStatus.value : 'INSTOCK'; products.value.push(product.value); toast.add({severity:'success', summary: 'Successful', detail: 'Product Created', life: 3000}); } productDialog.value = false; product.value = {}; } }; const editProduct = (prod) => { product.value = {...prod}; productDialog.value = true; }; const confirmDeleteProduct = (prod) => { product.value = prod; deleteProductDialog.value = true; }; const deleteProduct = () => { products.value = products.value.filter(val => val.id !== product.value.id); deleteProductDialog.value = false; product.value = {}; toast.add({severity:'success', summary: 'Successful', detail: 'Product Deleted', life: 3000}); }; const findIndexById = (id) => { let index = -1; for (let i = 0; i < products.value.length; i++) { if (products.value[i].id === id) { index = i; break; } } return index; }; const createId = () => { let id = ''; var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; for ( var i = 0; i < 5; i++ ) { id += chars.charAt(Math.floor(Math.random() * chars.length)); } return id; } const exportCSV = () => { dt.value.exportCSV(); }; const confirmDeleteSelected = () => { deleteProductsDialog.value = true; }; const deleteSelectedProducts = () => { products.value = products.value.filter(val => !selectedProducts.value.includes(val)); deleteProductsDialog.value = false; selectedProducts.value = null; toast.add({severity:'success', summary: 'Successful', detail: 'Products Deleted', life: 3000}); }; const getStatusLabel = (status) => { switch (status) { case 'INSTOCK': return 'success'; case 'LOWSTOCK': return 'warning'; case 'OUTOFSTOCK': return 'danger'; default: return null; } }; <\/script> `, data: ` { id: '1000', code: 'f230fh0g3', name: 'Bamboo Watch', description: 'Product Description', image: 'bamboo-watch.jpg', price: 65, category: 'Accessories', quantity: 24, inventoryStatus: 'INSTOCK', rating: 5 }, ...` } }; }, created() { this.initFilters(); }, mounted() { ProductService.getProducts().then((data) => (this.products = data)); }, methods: { formatCurrency(value) { if (value) return value.toLocaleString('en-US', { style: 'currency', currency: 'USD' }); return; }, openNew() { this.product = {}; this.submitted = false; this.productDialog = true; }, hideDialog() { this.productDialog = false; this.submitted = false; }, saveProduct() { this.submitted = true; if (this.product.name.trim()) { if (this.product.id) { this.product.inventoryStatus = this.product.inventoryStatus.value ? this.product.inventoryStatus.value : this.product.inventoryStatus; this.products[this.findIndexById(this.product.id)] = this.product; this.$toast.add({ severity: 'success', summary: 'Successful', detail: 'Product Updated', life: 3000 }); } else { this.product.id = this.createId(); this.product.code = this.createId(); this.product.image = 'product-placeholder.svg'; this.product.inventoryStatus = this.product.inventoryStatus ? this.product.inventoryStatus.value : 'INSTOCK'; this.products.push(this.product); this.$toast.add({ severity: 'success', summary: 'Successful', detail: 'Product Created', life: 3000 }); } this.productDialog = false; this.product = {}; } }, editProduct(product) { this.product = { ...product }; this.productDialog = true; }, confirmDeleteProduct(product) { this.product = product; this.deleteProductDialog = true; }, deleteProduct() { this.products = this.products.filter((val) => val.id !== this.product.id); this.deleteProductDialog = false; this.product = {}; this.$toast.add({ severity: 'success', summary: 'Successful', detail: 'Product Deleted', life: 3000 }); }, findIndexById(id) { let index = -1; for (let i = 0; i < this.products.length; i++) { if (this.products[i].id === id) { index = i; break; } } return index; }, createId() { let id = ''; var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; for (var i = 0; i < 5; i++) { id += chars.charAt(Math.floor(Math.random() * chars.length)); } return id; }, exportCSV() { this.$refs.dt.exportCSV(); }, confirmDeleteSelected() { this.deleteProductsDialog = true; }, deleteSelectedProducts() { this.products = this.products.filter((val) => !this.selectedProducts.includes(val)); this.deleteProductsDialog = false; this.selectedProducts = null; this.$toast.add({ severity: 'success', summary: 'Successful', detail: 'Products Deleted', life: 3000 }); }, initFilters() { this.filters = { global: { value: null, matchMode: FilterMatchMode.CONTAINS } }; }, getStatusLabel(status) { switch (status) { case 'INSTOCK': return 'success'; case 'LOWSTOCK': return 'warning'; case 'OUTOFSTOCK': return 'danger'; default: return null; } } } }; </script>