1
1
<script lang="ts" setup>
2
2
import { ref , computed } from ' vue'
3
3
import { useHead } from ' @vueuse/head'
4
+ import { Line , Bar } from ' vue-chartjs'
5
+ import {
6
+ Chart as ChartJS ,
7
+ CategoryScale ,
8
+ LinearScale ,
9
+ PointElement ,
10
+ LineElement ,
11
+ BarElement ,
12
+ Title ,
13
+ Tooltip ,
14
+ Legend ,
15
+ Filler ,
16
+ } from ' chart.js'
17
+
18
+ ChartJS .register (
19
+ CategoryScale ,
20
+ LinearScale ,
21
+ PointElement ,
22
+ LineElement ,
23
+ BarElement ,
24
+ Title ,
25
+ Tooltip ,
26
+ Legend ,
27
+ Filler ,
28
+ )
4
29
5
30
useHead ({
6
31
title: ' Dashboard - Commerce Gift Cards' ,
7
32
})
8
33
34
+ // Chart options
35
+ const chartOptions = {
36
+ responsive: true ,
37
+ maintainAspectRatio: false ,
38
+ plugins: {
39
+ legend: {
40
+ display: false ,
41
+ },
42
+ },
43
+ scales: {
44
+ y: {
45
+ beginAtZero: true ,
46
+ grid: {
47
+ display: true ,
48
+ color: ' rgba(0, 0, 0, 0.05)' ,
49
+ },
50
+ },
51
+ x: {
52
+ grid: {
53
+ display: false ,
54
+ },
55
+ },
56
+ },
57
+ }
58
+
59
+ // Generate monthly data for charts
60
+ const monthlyChartData = computed (() => {
61
+ const months = [' Jan' , ' Feb' , ' Mar' , ' Apr' , ' May' , ' Jun' , ' Jul' , ' Aug' , ' Sep' , ' Oct' , ' Nov' , ' Dec' ]
62
+
63
+ // Sample data - in a real app, this would be calculated from actual usage data
64
+ const salesData = [2 , 3 , 5 , 4 , 6 , 8 , 7 , 10 , 12 , 15 , 18 , 20 ]
65
+ const revenueData = [100 , 150 , 250 , 200 , 300 , 400 , 350 , 500 , 600 , 750 , 900 , 1000 ]
66
+
67
+ // Gift card sales chart data
68
+ const giftCardSalesData = {
69
+ labels: months ,
70
+ datasets: [
71
+ {
72
+ label: ' Gift Card Sales' ,
73
+ backgroundColor: ' rgba(59, 130, 246, 0.2)' ,
74
+ borderColor: ' rgba(59, 130, 246, 1)' ,
75
+ borderWidth: 2 ,
76
+ pointBackgroundColor: ' rgba(59, 130, 246, 1)' ,
77
+ pointBorderColor: ' #fff' ,
78
+ pointHoverBackgroundColor: ' #fff' ,
79
+ pointHoverBorderColor: ' rgba(59, 130, 246, 1)' ,
80
+ fill: true ,
81
+ tension: 0.4 ,
82
+ data: salesData ,
83
+ },
84
+ ],
85
+ }
86
+
87
+ // Gift card revenue chart data
88
+ const giftCardRevenueData = {
89
+ labels: months ,
90
+ datasets: [
91
+ {
92
+ label: ' Gift Card Revenue' ,
93
+ backgroundColor: ' rgba(16, 185, 129, 0.8)' ,
94
+ borderColor: ' rgba(16, 185, 129, 1)' ,
95
+ borderWidth: 1 ,
96
+ borderRadius: 4 ,
97
+ data: revenueData ,
98
+ },
99
+ ],
100
+ }
101
+
102
+ return {
103
+ giftCardSalesData ,
104
+ giftCardRevenueData
105
+ }
106
+ })
107
+
108
+ // Time range selector
109
+ const timeRange = ref (' Last 30 days' )
110
+ const timeRanges = [' Today' , ' Last 7 days' , ' Last 30 days' , ' Last 90 days' , ' Last year' , ' All time' ]
111
+
9
112
// Define gift card type
10
113
interface GiftCard {
11
114
id: number
@@ -102,6 +205,33 @@ const statusFilter = ref('all')
102
205
// Available statuses
103
206
const statuses = [' all' , ' Active' , ' Used' , ' Expired' ]
104
207
208
+ // Computed gift card statistics
209
+ const giftCardStats = computed (() => {
210
+ const activeGiftCards = giftCards .value .filter (c => c .status === ' Active' ).length
211
+ const totalGiftCards = giftCards .value .length
212
+
213
+ // Calculate total initial value and current balance
214
+ const totalInitialValue = giftCards .value .reduce ((sum , card ) => sum + card .initialValue , 0 )
215
+ const totalCurrentBalance = giftCards .value .reduce ((sum , card ) => sum + card .currentBalance , 0 )
216
+
217
+ // Calculate amount redeemed
218
+ const totalRedeemed = totalInitialValue - totalCurrentBalance
219
+
220
+ // Calculate redemption rate
221
+ const redemptionRate = totalInitialValue > 0
222
+ ? ((totalRedeemed / totalInitialValue ) * 100 ).toFixed (1 )
223
+ : ' 0.0'
224
+
225
+ return {
226
+ activeGiftCards ,
227
+ totalGiftCards ,
228
+ totalInitialValue: totalInitialValue .toFixed (2 ),
229
+ totalCurrentBalance: totalCurrentBalance .toFixed (2 ),
230
+ totalRedeemed: totalRedeemed .toFixed (2 ),
231
+ redemptionRate
232
+ }
233
+ })
234
+
105
235
// Computed filtered and sorted gift cards
106
236
const filteredGiftCards = computed (() => {
107
237
return giftCards .value
@@ -158,27 +288,29 @@ function getStatusClass(status: string): string {
158
288
}
159
289
}
160
290
291
+ // Define global date variables
292
+ const currentDate = new Date ().toISOString ().split (' T' )[0 ] as string
293
+ const oneYearFromNow = new Date ()
294
+ oneYearFromNow .setFullYear (oneYearFromNow .getFullYear () + 1 )
295
+ const futureDate = oneYearFromNow .toISOString ().split (' T' )[0 ] as string
296
+
161
297
// Modal state
162
298
const showAddModal = ref (false )
163
299
const newGiftCard = ref <NewGiftCard >({
164
300
code: ' ' ,
165
301
initialValue: 50 ,
166
302
recipient: ' ' ,
167
303
email: ' ' ,
168
- expiryDate: ' '
304
+ expiryDate: futureDate
169
305
})
170
306
171
307
function openAddModal(): void {
172
- // Set expiry date to one year from now
173
- const oneYearFromNow = new Date ()
174
- oneYearFromNow .setFullYear (oneYearFromNow .getFullYear () + 1 )
175
-
176
308
newGiftCard .value = {
177
309
code: generateGiftCardCode (),
178
310
initialValue: 50 ,
179
311
recipient: ' ' ,
180
312
email: ' ' ,
181
- expiryDate: oneYearFromNow . toISOString (). split ( ' T ' )[ 0 ]
313
+ expiryDate: futureDate
182
314
}
183
315
showAddModal .value = true
184
316
}
@@ -216,13 +348,12 @@ function generateGiftCardCode(): string {
216
348
function addGiftCard(): void {
217
349
// In a real app, this would send data to the server
218
350
const id = Math .max (... giftCards .value .map (c => c .id )) + 1
219
- const today = new Date ().toISOString ().split (' T' )[0 ]
220
351
221
352
// Ensure we have non-null values with default values
222
353
const code = newGiftCard .value .code || generateGiftCardCode ()
223
354
const recipient = newGiftCard .value .recipient || ' Anonymous'
224
355
const email = newGiftCard .value .email || ' '
225
- const expiryDate = newGiftCard .value .expiryDate || today
356
+ const expiryDate = newGiftCard .value .expiryDate || futureDate
226
357
227
358
giftCards .value .push ({
228
359
id ,
@@ -232,7 +363,7 @@ function addGiftCard(): void {
232
363
recipient ,
233
364
email ,
234
365
purchasedBy: ' Current User' , // In a real app, this would be the logged-in user
235
- purchaseDate: today ,
366
+ purchaseDate: currentDate ,
236
367
expiryDate ,
237
368
status: ' Active'
238
369
})
@@ -263,8 +394,80 @@ function addGiftCard(): void {
263
394
</div >
264
395
</div >
265
396
397
+ <!-- Time range selector -->
398
+ <div class =" mt-4 flex items-center justify-between" >
399
+ <p class =" text-sm text-gray-500 dark:text-gray-400" >
400
+ Overview of your gift card performance
401
+ </p >
402
+ <div class =" relative" >
403
+ <select v-model =" timeRange" class =" block w-full rounded-md border-0 py-1.5 pl-3 pr-10 text-gray-900 ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-blue-600 sm:text-sm sm:leading-6 dark:bg-blue-gray-800 dark:text-white dark:ring-gray-700" >
404
+ <option v-for =" range in timeRanges" :key =" range" :value =" range" >{{ range }}</option >
405
+ </select >
406
+ </div >
407
+ </div >
408
+
409
+ <!-- Stats -->
410
+ <dl class =" mt-5 grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-4" >
411
+ <div class =" overflow-hidden rounded-lg bg-white px-4 py-5 shadow sm:p-6 dark:bg-blue-gray-800" >
412
+ <dt class =" truncate text-sm font-medium text-gray-500 dark:text-gray-300" >Active Gift Cards</dt >
413
+ <dd class =" mt-1 text-3xl font-semibold tracking-tight text-gray-900 dark:text-white" >{{ giftCardStats.activeGiftCards }}</dd >
414
+ <dd class =" mt-2 flex items-center text-sm text-green-600 dark:text-green-400" >
415
+ <div class =" i-hugeicons-analytics-up h-4 w-4 mr-1" ></div >
416
+ <span >{{ Math.round(giftCardStats.activeGiftCards / giftCardStats.totalGiftCards * 100) }}% of total</span >
417
+ </dd >
418
+ </div >
419
+
420
+ <div class =" overflow-hidden rounded-lg bg-white px-4 py-5 shadow sm:p-6 dark:bg-blue-gray-800" >
421
+ <dt class =" truncate text-sm font-medium text-gray-500 dark:text-gray-300" >Total Value</dt >
422
+ <dd class =" mt-1 text-3xl font-semibold tracking-tight text-gray-900 dark:text-white" >${{ parseFloat(giftCardStats.totalInitialValue).toLocaleString() }}</dd >
423
+ <dd class =" mt-2 flex items-center text-sm text-green-600 dark:text-green-400" >
424
+ <div class =" i-hugeicons-analytics-up h-4 w-4 mr-1" ></div >
425
+ <span >10.5% increase</span >
426
+ </dd >
427
+ </div >
428
+
429
+ <div class =" overflow-hidden rounded-lg bg-white px-4 py-5 shadow sm:p-6 dark:bg-blue-gray-800" >
430
+ <dt class =" truncate text-sm font-medium text-gray-500 dark:text-gray-300" >Current Balance</dt >
431
+ <dd class =" mt-1 text-3xl font-semibold tracking-tight text-gray-900 dark:text-white" >${{ parseFloat(giftCardStats.totalCurrentBalance).toLocaleString() }}</dd >
432
+ <dd class =" mt-2 flex items-center text-sm text-blue-600 dark:text-blue-400" >
433
+ <div class =" i-hugeicons-analytics-up h-4 w-4 mr-1" ></div >
434
+ <span >${{ parseFloat(giftCardStats.totalRedeemed).toLocaleString() }} redeemed</span >
435
+ </dd >
436
+ </div >
437
+
438
+ <div class =" overflow-hidden rounded-lg bg-white px-4 py-5 shadow sm:p-6 dark:bg-blue-gray-800" >
439
+ <dt class =" truncate text-sm font-medium text-gray-500 dark:text-gray-300" >Redemption Rate</dt >
440
+ <dd class =" mt-1 text-3xl font-semibold tracking-tight text-gray-900 dark:text-white" >{{ giftCardStats.redemptionRate }}%</dd >
441
+ <dd class =" mt-2 flex items-center text-sm text-green-600 dark:text-green-400" >
442
+ <div class =" i-hugeicons-analytics-up h-4 w-4 mr-1" ></div >
443
+ <span >5.2% increase</span >
444
+ </dd >
445
+ </div >
446
+ </dl >
447
+
448
+ <!-- Charts -->
449
+ <div class =" mt-8 grid grid-cols-1 gap-5 lg:grid-cols-2" >
450
+ <div class =" overflow-hidden rounded-lg bg-white shadow dark:bg-blue-gray-800" >
451
+ <div class =" p-6" >
452
+ <h3 class =" text-base font-semibold leading-6 text-gray-900 dark:text-white" >Gift Card Sales</h3 >
453
+ <div class =" mt-2 h-80" >
454
+ <Line :data =" monthlyChartData.giftCardSalesData" :options =" chartOptions" />
455
+ </div >
456
+ </div >
457
+ </div >
458
+
459
+ <div class =" overflow-hidden rounded-lg bg-white shadow dark:bg-blue-gray-800" >
460
+ <div class =" p-6" >
461
+ <h3 class =" text-base font-semibold leading-6 text-gray-900 dark:text-white" >Gift Card Revenue</h3 >
462
+ <div class =" mt-2 h-80" >
463
+ <Bar :data =" monthlyChartData.giftCardRevenueData" :options =" chartOptions" />
464
+ </div >
465
+ </div >
466
+ </div >
467
+ </div >
468
+
266
469
<!-- Filters -->
267
- <div class =" mt-6 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4" >
470
+ <div class =" mt-8 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4" >
268
471
<div class =" relative max-w-sm" >
269
472
<div class =" pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3" >
270
473
<div class =" i-hugeicons-search-01 h-5 w-5 text-gray-400" ></div >
@@ -292,6 +495,12 @@ function addGiftCard(): void {
292
495
293
496
<!-- Gift Cards table -->
294
497
<div class =" mt-6 flow-root" >
498
+ <div class =" sm:flex sm:items-center sm:justify-between mb-4" >
499
+ <h3 class =" text-base font-semibold leading-6 text-gray-900 dark:text-white" >All Gift Cards</h3 >
500
+ <p class =" mt-1 text-sm text-gray-500 dark:text-gray-400" >
501
+ A list of all the gift cards in your store including their value, balance, and status.
502
+ </p >
503
+ </div >
295
504
<div class =" -mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8" >
296
505
<div class =" inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8" >
297
506
<div class =" overflow-hidden shadow ring-1 ring-black ring-opacity-5 sm:rounded-lg" >
0 commit comments