1
1
<script lang="ts" setup>
2
2
import { ref , computed } from ' vue'
3
3
import { useHead } from ' @vueuse/head'
4
+ import { useLocalStorage } from ' @vueuse/core'
4
5
5
6
useHead ({
6
7
title: ' Dashboard - Commerce Products' ,
@@ -14,6 +15,7 @@ interface Product {
14
15
price: number
15
16
salePrice: number | null
16
17
category: string
18
+ manufacturer: string
17
19
tags: string []
18
20
imageUrl: string
19
21
inventory: number
@@ -41,6 +43,7 @@ const products = ref<Product[]>([
41
43
price: 12.99 ,
42
44
salePrice: null ,
43
45
category: ' Burgers' ,
46
+ manufacturer: ' Burger Joint' ,
44
47
tags: [' beef' , ' cheese' , ' popular' ],
45
48
imageUrl: ' https://images.unsplash.com/photo-1568901346375-23c9450c58cd?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=500&h=500&q=80' ,
46
49
inventory: 100 ,
@@ -57,6 +60,7 @@ const products = ref<Product[]>([
57
60
price: 14.99 ,
58
61
salePrice: 12.99 ,
59
62
category: ' Pizza' ,
63
+ manufacturer: ' Pizza Palace' ,
60
64
tags: [' vegetarian' , ' italian' , ' popular' ],
61
65
imageUrl: ' https://images.unsplash.com/photo-1604382354936-07c5d9983bd3?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=500&h=500&q=80' ,
62
66
inventory: 80 ,
@@ -73,6 +77,7 @@ const products = ref<Product[]>([
73
77
price: 11.99 ,
74
78
salePrice: null ,
75
79
category: ' Salads' ,
80
+ manufacturer: ' Fresh Greens' ,
76
81
tags: [' chicken' , ' healthy' ],
77
82
imageUrl: ' https://images.unsplash.com/photo-1550304943-4f24f54ddde9?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=500&h=500&q=80' ,
78
83
inventory: 50 ,
@@ -89,6 +94,7 @@ const products = ref<Product[]>([
89
94
price: 15.99 ,
90
95
salePrice: null ,
91
96
category: ' Asian' ,
97
+ manufacturer: ' Noodle House' ,
92
98
tags: [' spicy' , ' japanese' , ' popular' ],
93
99
imageUrl: ' https://images.unsplash.com/photo-1569718212165-3a8278d5f624?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=500&h=500&q=80' ,
94
100
inventory: 60 ,
@@ -105,6 +111,7 @@ const products = ref<Product[]>([
105
111
price: 9.99 ,
106
112
salePrice: 8.49 ,
107
113
category: ' Wraps' ,
114
+ manufacturer: ' Wrap Masters' ,
108
115
tags: [' vegetarian' , ' healthy' ],
109
116
imageUrl: ' https://images.unsplash.com/photo-1626700051175-6818013e1d4f?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=500&h=500&q=80' ,
110
117
inventory: 40 ,
@@ -121,6 +128,7 @@ const products = ref<Product[]>([
121
128
price: 13.99 ,
122
129
salePrice: null ,
123
130
category: ' Sandwiches' ,
131
+ manufacturer: ' Smokey BBQ' ,
124
132
tags: [' pork' , ' bbq' ],
125
133
imageUrl: ' https://images.unsplash.com/photo-1513185041617-8ab03f83d6c5?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=500&h=500&q=80' ,
126
134
inventory: 35 ,
@@ -137,6 +145,7 @@ const products = ref<Product[]>([
137
145
price: 8.99 ,
138
146
salePrice: null ,
139
147
category: ' Desserts' ,
148
+ manufacturer: ' Sweet Treats' ,
140
149
tags: [' chocolate' , ' sweet' , ' popular' ],
141
150
imageUrl: ' https://images.unsplash.com/photo-1563805042-7684c019e1cb?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=500&h=500&q=80' ,
142
151
inventory: 45 ,
@@ -153,6 +162,7 @@ const products = ref<Product[]>([
153
162
price: 5.99 ,
154
163
salePrice: null ,
155
164
category: ' Beverages' ,
165
+ manufacturer: ' Coffee Co.' ,
156
166
tags: [' coffee' , ' cold' ],
157
167
imageUrl: ' https://images.unsplash.com/photo-1461023058943-07fcbe16d735?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=500&h=500&q=80' ,
158
168
inventory: 120 ,
@@ -169,6 +179,7 @@ const products = ref<Product[]>([
169
179
price: 16.99 ,
170
180
salePrice: 14.99 ,
171
181
category: ' Appetizers' ,
182
+ manufacturer: ' Wing World' ,
172
183
tags: [' chicken' , ' spicy' , ' popular' ],
173
184
imageUrl: ' https://images.unsplash.com/photo-1608039755401-742074f0548d?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=500&h=500&q=80' ,
174
185
inventory: 70 ,
@@ -185,6 +196,7 @@ const products = ref<Product[]>([
185
196
price: 13.49 ,
186
197
salePrice: null ,
187
198
category: ' Asian' ,
199
+ manufacturer: ' Wok & Roll' ,
188
200
tags: [' vegetarian' , ' healthy' ],
189
201
imageUrl: ' https://images.unsplash.com/photo-1512621776951-a57141f2eefd?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=500&h=500&q=80' ,
190
202
inventory: 30 ,
@@ -201,6 +213,7 @@ const products = ref<Product[]>([
201
213
price: 10.99 ,
202
214
salePrice: null ,
203
215
category: ' Mexican' ,
216
+ manufacturer: ' Taco Time' ,
204
217
tags: [' beef' , ' spicy' ],
205
218
imageUrl: ' https://images.unsplash.com/photo-1551504734-5ee1c4a1479b?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=500&h=500&q=80' ,
206
219
inventory: 55 ,
@@ -217,6 +230,7 @@ const products = ref<Product[]>([
217
230
price: 7.99 ,
218
231
salePrice: null ,
219
232
category: ' Desserts' ,
233
+ manufacturer: ' Sweet Treats' ,
220
234
tags: [' sweet' , ' popular' ],
221
235
imageUrl: ' https://images.unsplash.com/photo-1565958011703-44f9829ba187?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=500&h=500&q=80' ,
222
236
inventory: 25 ,
@@ -251,7 +265,7 @@ const sortBy = ref('dateAdded')
251
265
const sortOrder = ref (' desc' )
252
266
const categoryFilter = ref (' all' )
253
267
const statusFilter = ref (' all' )
254
- const viewMode = ref ( ' grid ' ) // 'grid' or ' list'
268
+ const viewMode = useLocalStorage ( ' products-view-mode ' , ' list ' ) // Default to list view and save in localStorage
255
269
256
270
// Available statuses
257
271
const statuses = [' all' , ' Active' , ' Low Stock' , ' Out of Stock' , ' Discontinued' ]
@@ -360,6 +374,7 @@ function openAddProductModal(): void {
360
374
price: 0 ,
361
375
salePrice: null ,
362
376
category: ' ' ,
377
+ manufacturer: ' ' ,
363
378
tags: [],
364
379
imageUrl: ' ' ,
365
380
inventory: 0 ,
@@ -403,6 +418,16 @@ function saveProduct(): void {
403
418
const totalProducts = computed (() => products .value .length )
404
419
const featuredProducts = computed (() => products .value .filter (p => p .featured ).length )
405
420
const lowStockProducts = computed (() => products .value .filter (p => p .status === ' Low Stock' ).length )
421
+
422
+ // Function to delete a product
423
+ function deleteProduct(productId : number ): void {
424
+ if (confirm (' Are you sure you want to delete this product?' )) {
425
+ const index = products .value .findIndex (p => p .id === productId )
426
+ if (index !== - 1 ) {
427
+ products .value .splice (index , 1 )
428
+ }
429
+ }
430
+ }
406
431
</script >
407
432
408
433
<template >
@@ -695,18 +720,20 @@ const lowStockProducts = computed(() => products.value.filter(p => p.status ===
695
720
<thead class =" bg-gray-50 dark:bg-blue-gray-700" >
696
721
<tr >
697
722
<th scope =" col" class =" py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 dark:text-white sm:pl-6" >Product</th >
723
+ <th scope =" col" class =" px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-white" >Manufacturer</th >
698
724
<th scope =" col" class =" px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-white" >Category</th >
699
725
<th scope =" col" class =" px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-white" >Price</th >
700
726
<th scope =" col" class =" px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-white" >Status</th >
701
- <th scope =" col" class =" px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-white" >Inventory</th >
727
+ <th scope =" col" class =" px-3 py-3.5 text-right text-sm font-semibold text-gray-900 dark:text-white" >Inventory</th >
728
+ <th scope =" col" class =" px-3 py-3.5 text-right text-sm font-semibold text-gray-900 dark:text-white" >Created At</th >
702
729
<th scope =" col" class =" relative py-3.5 pl-3 pr-4 sm:pr-6" >
703
730
<span class =" sr-only" >Actions</span >
704
731
</th >
705
732
</tr >
706
733
</thead >
707
734
<tbody class =" divide-y divide-gray-200 bg-white dark:divide-gray-700 dark:bg-blue-gray-800" >
708
735
<tr v-if =" paginatedProducts.length === 0" >
709
- <td colspan =" 6 " class =" py-4 text-center text-sm text-gray-500 dark:text-gray-400" >
736
+ <td colspan =" 8 " class =" py-4 text-center text-sm text-gray-500 dark:text-gray-400" >
710
737
No products found. Try adjusting your search or filter.
711
738
</td >
712
739
</tr >
@@ -728,6 +755,7 @@ const lowStockProducts = computed(() => products.value.filter(p => p.status ===
728
755
</div >
729
756
</div >
730
757
</td >
758
+ <td class =" whitespace-nowrap px-3 py-4 text-sm text-gray-500 dark:text-gray-400" >{{ product.manufacturer }}</td >
731
759
<td class =" whitespace-nowrap px-3 py-4 text-sm text-gray-500 dark:text-gray-400" >{{ product.category }}</td >
732
760
<td class =" whitespace-nowrap px-3 py-4 text-sm" >
733
761
<div v-if =" product.salePrice !== null" >
@@ -741,14 +769,25 @@ const lowStockProducts = computed(() => products.value.filter(p => p.status ===
741
769
{{ product.status }}
742
770
</span >
743
771
</td >
744
- <td class =" whitespace-nowrap px-3 py-4 text-sm text-gray-500 dark:text-gray-400" >{{ product.inventory }}</td >
772
+ <td class =" whitespace-nowrap px-3 py-4 text-sm text-gray-500 dark:text-gray-400 text-right" >{{ product.inventory }}</td >
773
+ <td class =" whitespace-nowrap px-3 py-4 text-sm text-gray-500 dark:text-gray-400 text-right" >{{ product.dateAdded }}</td >
745
774
<td class =" relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6" >
746
- <button
747
- @click =" openEditProductModal(product)"
748
- class =" text-blue-600 hover:text-blue-900 dark:text-blue-400 dark:hover:text-blue-300"
749
- >
750
- Edit<span class =" sr-only" >, {{ product.name }}</span >
751
- </button >
775
+ <div class =" flex justify-end space-x-2" >
776
+ <button
777
+ @click =" openEditProductModal(product)"
778
+ class =" text-blue-600 hover:text-blue-900 dark:text-blue-400 dark:hover:text-blue-300"
779
+ >
780
+ <div class =" i-hugeicons-pencil-01 h-5 w-5" ></div >
781
+ <span class =" sr-only" >Edit {{ product.name }}</span >
782
+ </button >
783
+ <button
784
+ @click =" deleteProduct(product.id)"
785
+ class =" text-red-600 hover:text-red-900 dark:text-red-400 dark:hover:text-red-300"
786
+ >
787
+ <div class =" i-hugeicons-trash-01 h-5 w-5" ></div >
788
+ <span class =" sr-only" >Delete {{ product.name }}</span >
789
+ </button >
790
+ </div >
752
791
</td >
753
792
</tr >
754
793
</tbody >
@@ -900,7 +939,7 @@ const lowStockProducts = computed(() => products.value.filter(p => p.status ===
900
939
</div >
901
940
</div >
902
941
903
- <!-- Category and inventory -->
942
+ <!-- Category and manufacturer -->
904
943
<div class =" grid grid-cols-2 gap-4" >
905
944
<div >
906
945
<label for =" product-category" class =" block text-sm font-medium text-gray-700 dark:text-gray-300" >Category</label >
@@ -912,6 +951,20 @@ const lowStockProducts = computed(() => products.value.filter(p => p.status ===
912
951
required
913
952
/>
914
953
</div >
954
+ <div >
955
+ <label for =" product-manufacturer" class =" block text-sm font-medium text-gray-700 dark:text-gray-300" >Manufacturer</label >
956
+ <input
957
+ type =" text"
958
+ id =" product-manufacturer"
959
+ v-model =" currentProduct.manufacturer"
960
+ class =" mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm dark:bg-blue-gray-700 dark:border-gray-600 dark:text-white"
961
+ required
962
+ />
963
+ </div >
964
+ </div >
965
+
966
+ <!-- Inventory and status -->
967
+ <div class =" grid grid-cols-2 gap-4" >
915
968
<div >
916
969
<label for =" product-inventory" class =" block text-sm font-medium text-gray-700 dark:text-gray-300" >Inventory</label >
917
970
<input
@@ -923,6 +976,16 @@ const lowStockProducts = computed(() => products.value.filter(p => p.status ===
923
976
required
924
977
/>
925
978
</div >
979
+ <div >
980
+ <label for =" product-status" class =" block text-sm font-medium text-gray-700 dark:text-gray-300" >Status</label >
981
+ <select
982
+ id =" product-status"
983
+ v-model =" currentProduct.status"
984
+ class =" mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm dark:bg-blue-gray-700 dark:border-gray-600 dark:text-white"
985
+ >
986
+ <option v-for =" status in statuses.slice(1)" :key =" status" :value =" status" >{{ status }}</option >
987
+ </select >
988
+ </div >
926
989
</div >
927
990
928
991
<!-- Status and featured -->
0 commit comments