@@ -15,7 +15,8 @@ const categories = ref([
15
15
description: ' Traditional and specialty pizzas' ,
16
16
productCount: 24 ,
17
17
featured: true ,
18
- createdAt: ' 2023-05-10'
18
+ createdAt: ' 2023-05-10' ,
19
+ image: ' /images/categories/pizza.jpg'
19
20
},
20
21
{
21
22
id: 2 ,
@@ -24,7 +25,8 @@ const categories = ref([
24
25
description: ' Gourmet and classic burgers' ,
25
26
productCount: 18 ,
26
27
featured: true ,
27
- createdAt: ' 2023-05-15'
28
+ createdAt: ' 2023-05-15' ,
29
+ image: ' '
28
30
},
29
31
{
30
32
id: 3 ,
@@ -33,7 +35,8 @@ const categories = ref([
33
35
description: ' Fresh sushi, rolls, and Japanese cuisine' ,
34
36
productCount: 32 ,
35
37
featured: true ,
36
- createdAt: ' 2023-06-01'
38
+ createdAt: ' 2023-06-01' ,
39
+ image: ' /images/categories/sushi.jpg'
37
40
},
38
41
{
39
42
id: 4 ,
@@ -42,7 +45,8 @@ const categories = ref([
42
45
description: ' Authentic Mexican dishes and street food' ,
43
46
productCount: 22 ,
44
47
featured: false ,
45
- createdAt: ' 2023-06-10'
48
+ createdAt: ' 2023-06-10' ,
49
+ image: ' '
46
50
},
47
51
{
48
52
id: 5 ,
@@ -51,7 +55,8 @@ const categories = ref([
51
55
description: ' Italian pasta dishes and specialties' ,
52
56
productCount: 16 ,
53
57
featured: false ,
54
- createdAt: ' 2023-06-20'
58
+ createdAt: ' 2023-06-20' ,
59
+ image: ' /images/categories/pasta.jpg'
55
60
},
56
61
{
57
62
id: 6 ,
@@ -60,7 +65,8 @@ const categories = ref([
60
65
description: ' Nutritious, plant-based, and health-conscious options' ,
61
66
productCount: 28 ,
62
67
featured: true ,
63
- createdAt: ' 2023-07-05'
68
+ createdAt: ' 2023-07-05' ,
69
+ image: ' /images/categories/healthy.jpg'
64
70
},
65
71
{
66
72
id: 7 ,
@@ -69,7 +75,8 @@ const categories = ref([
69
75
description: ' Sweet treats, cakes, and pastries' ,
70
76
productCount: 35 ,
71
77
featured: false ,
72
- createdAt: ' 2023-07-15'
78
+ createdAt: ' 2023-07-15' ,
79
+ image: ' '
73
80
},
74
81
{
75
82
id: 8 ,
@@ -78,7 +85,8 @@ const categories = ref([
78
85
description: ' Coffee, tea, smoothies, and specialty drinks' ,
79
86
productCount: 19 ,
80
87
featured: false ,
81
- createdAt: ' 2023-08-01'
88
+ createdAt: ' 2023-08-01' ,
89
+ image: ' /images/categories/beverages.jpg'
82
90
},
83
91
{
84
92
id: 9 ,
@@ -87,7 +95,8 @@ const categories = ref([
87
95
description: ' Starters, small plates, and shareable items' ,
88
96
productCount: 26 ,
89
97
featured: true ,
90
- createdAt: ' 2023-08-10'
98
+ createdAt: ' 2023-08-10' ,
99
+ image: ' /images/categories/appetizers.jpg'
91
100
},
92
101
{
93
102
id: 10 ,
@@ -96,7 +105,8 @@ const categories = ref([
96
105
description: ' Creative dishes combining Asian culinary traditions' ,
97
106
productCount: 21 ,
98
107
featured: false ,
99
- createdAt: ' 2023-08-20'
108
+ createdAt: ' 2023-08-20' ,
109
+ image: ' '
100
110
}
101
111
])
102
112
@@ -152,17 +162,45 @@ const newCategory = ref<{
152
162
name: string ;
153
163
description: string ;
154
164
featured: boolean ;
165
+ image: string ;
155
166
}>({
156
167
name: ' ' ,
157
168
description: ' ' ,
158
- featured: false
169
+ featured: false ,
170
+ image: ' '
159
171
})
160
172
173
+ // Image preview helper
174
+ const imagePreview = computed (() => {
175
+ return newCategory .value .image || ' /images/categories/placeholder.jpg'
176
+ })
177
+
178
+ // Handle file upload
179
+ function handleImageUpload(event : Event ): void {
180
+ const input = event .target as HTMLInputElement
181
+ if (input .files && input .files [0 ]) {
182
+ const file = input .files [0 ]
183
+
184
+ // In a real application, you would upload the file to a server
185
+ // and get back a URL. For this demo, we'll simulate that by
186
+ // creating a local object URL.
187
+ const reader = new FileReader ()
188
+ reader .onload = (e ) => {
189
+ if (e .target && e .target .result ) {
190
+ // In a real app, this would be the URL returned from the server
191
+ newCategory .value .image = e .target .result as string
192
+ }
193
+ }
194
+ reader .readAsDataURL (file )
195
+ }
196
+ }
197
+
161
198
function openAddModal(): void {
162
199
newCategory .value = {
163
200
name: ' ' ,
164
201
description: ' ' ,
165
- featured: false
202
+ featured: false ,
203
+ image: ' '
166
204
}
167
205
showAddModal .value = true
168
206
}
@@ -183,10 +221,19 @@ function addCategory(): void {
183
221
description: newCategory .value .description || ' ' ,
184
222
productCount: 0 ,
185
223
featured: newCategory .value .featured ,
186
- createdAt: currentDate
224
+ createdAt: currentDate ,
225
+ image: newCategory .value .image || ' /images/categories/placeholder.jpg'
187
226
})
188
227
closeAddModal ()
189
228
}
229
+
230
+ // Color mapping for initial letters
231
+ const initialColors: Record <string , string > = {
232
+ ' Burgers' : ' bg-red-500' ,
233
+ ' Mexican' : ' bg-green-500' ,
234
+ ' Desserts' : ' bg-purple-500' ,
235
+ ' Asian Fusion' : ' bg-blue-500'
236
+ }
190
237
</script >
191
238
192
239
<template >
@@ -291,8 +338,26 @@ function addCategory(): void {
291
338
</thead >
292
339
<tbody class =" divide-y divide-gray-200 bg-white dark:divide-gray-700 dark:bg-blue-gray-800" >
293
340
<tr v-for =" category in filteredCategories" :key =" category.id" >
294
- <td class =" whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:pl-6 dark:text-white" >
295
- {{ category.name }}
341
+ <td class =" whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-900 dark:text-white" >
342
+ <div class =" flex items-center space-x-3" >
343
+ <template v-if =" category .image " >
344
+ <img
345
+ :src =" category.image"
346
+ :alt =" category.name"
347
+ class =" h-10 w-10 rounded-full object-cover border border-gray-200 dark:border-gray-700 shadow-sm"
348
+ onerror =" this.src='/images/categories/placeholder.jpg'"
349
+ />
350
+ </template >
351
+ <template v-else >
352
+ <div
353
+ :class =" ['h-10 w-10 rounded-full flex items-center justify-center text-white font-medium shadow-sm',
354
+ initialColors[category.name] || 'bg-blue-500']"
355
+ >
356
+ {{ category.name ? category.name.charAt(0).toUpperCase() : '?' }}
357
+ </div >
358
+ </template >
359
+ <span >{{ category.name }}</span >
360
+ </div >
296
361
</td >
297
362
<td class =" px-3 py-4 text-sm text-gray-500 dark:text-gray-300" >
298
363
{{ category.description }}
@@ -310,7 +375,7 @@ function addCategory(): void {
310
375
<td class =" relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6" >
311
376
<div class =" flex items-center justify-end space-x-2" >
312
377
<button type =" button" class =" text-blue-600 hover:text-blue-900 dark:text-blue-400 dark:hover:text-blue-300" >
313
- <div class =" i-hugeicons-license-draft h-5 w-5" ></div >
378
+ <div class =" i-hugeicons-edit-01 h-5 w-5" ></div >
314
379
</button >
315
380
<button type =" button" class =" text-red-600 hover:text-red-900 dark:text-red-400 dark:hover:text-red-300" >
316
381
<div class =" i-hugeicons-waste h-5 w-5" ></div >
@@ -365,6 +430,49 @@ function addCategory(): void {
365
430
></textarea >
366
431
</div >
367
432
</div >
433
+ <div >
434
+ <label for =" category-image" class =" block text-sm font-medium leading-6 text-gray-900 dark:text-gray-200 text-left" >Image</label >
435
+ <div class =" mt-2" >
436
+ <div class =" flex items-center gap-3" >
437
+ <input
438
+ type =" text"
439
+ id =" category-image"
440
+ v-model =" newCategory.image"
441
+ placeholder =" /images/categories/your-category.jpg"
442
+ class =" block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-blue-600 sm:text-sm sm:leading-6 dark:bg-blue-gray-700 dark:text-white dark:ring-gray-600"
443
+ />
444
+ <span class =" text-gray-500 dark:text-gray-400" >or</span >
445
+ <label for =" file-upload" class =" cursor-pointer rounded-md bg-white px-2.5 py-1.5 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 dark:bg-blue-gray-700 dark:text-white dark:ring-gray-600 dark:hover:bg-blue-gray-600" >
446
+ Browse
447
+ <input
448
+ id =" file-upload"
449
+ type =" file"
450
+ accept =" image/*"
451
+ class =" sr-only"
452
+ @change =" handleImageUpload"
453
+ />
454
+ </label >
455
+ </div >
456
+ </div >
457
+ <div class =" mt-2 flex justify-center" >
458
+ <template v-if =" newCategory .image " >
459
+ <img
460
+ :src =" imagePreview"
461
+ alt =" Category preview"
462
+ class =" h-16 w-16 rounded-full object-cover border border-gray-200 dark:border-gray-700 shadow-sm"
463
+ onerror =" this.src='/images/categories/placeholder.jpg'"
464
+ />
465
+ </template >
466
+ <template v-else >
467
+ <div
468
+ class =" h-16 w-16 rounded-full flex items-center justify-center text-white font-medium shadow-sm bg-blue-500"
469
+ >
470
+ {{ newCategory.name ? newCategory.name.charAt(0).toUpperCase() : '?' }}
471
+ </div >
472
+ </template >
473
+ </div >
474
+ <p class =" mt-1 text-xs text-gray-500 dark:text-gray-400 text-left" >Enter the URL of the category image or upload a file</p >
475
+ </div >
368
476
<div class =" flex items-center" >
369
477
<input
370
478
id =" category-featured"
0 commit comments