@@ -5,6 +5,7 @@ import { JobStatus } from '../types/job'
5
5
6
6
const queueStore = useQueueStore ()
7
7
const error = ref <string | null >(null )
8
+ const isLoading = ref (false )
8
9
9
10
// Mock data for charts
10
11
const processingRateData = ref ([42 , 50 , 65 , 59 , 80 , 81 , 56 , 55 , 72 , 64 , 61 , 68 , 75 , 62 , 44 , 35 , 41 , 48 ])
@@ -30,39 +31,52 @@ const queueActivityData = computed(() => {
30
31
}))
31
32
})
32
33
34
+ // Mock data for groups and batches
35
+ const groupData = ref ([
36
+ { name: ' Daily Reports' , jobCount: 45 , completionRate: 78 , activeJobs: 3 },
37
+ { name: ' User Notifications' , jobCount: 128 , completionRate: 62 , activeJobs: 12 },
38
+ { name: ' Data Exports' , jobCount: 34 , completionRate: 91 , activeJobs: 1 },
39
+ ])
40
+
41
+ const recentBatches = ref ([
42
+ { id: ' batch_abc123' , name: ' Weekly Newsletter' , jobCount: 15 , completedJobs: 12 , status: ' active' },
43
+ { id: ' batch_def456' , name: ' Image Processing' , jobCount: 24 , completedJobs: 24 , status: ' completed' },
44
+ { id: ' batch_ghi789' , name: ' User Import' , jobCount: 120 , completedJobs: 98 , status: ' active' },
45
+ ])
46
+
33
47
onMounted (async () => {
48
+ await fetchDashboardData ()
49
+ })
50
+
51
+ async function fetchDashboardData(forceRefresh = false ) {
52
+ error .value = null
53
+ isLoading .value = true
54
+
34
55
try {
35
56
await Promise .all ([
36
- queueStore .fetchQueueStats (),
37
- queueStore .fetchQueues (),
57
+ queueStore .fetchQueueStats (forceRefresh ),
58
+ queueStore .fetchQueues (forceRefresh ),
38
59
])
39
60
40
61
// Generate processing rate data (last 18 minutes)
41
62
processingRateData .value = Array .from ({ length: 18 }, () =>
42
63
Math .max (20 , Math .floor (Math .random () * queueStore .stats .processingRate * 1.5 )))
64
+
65
+ // In a real implementation, you would fetch groups and batches data here
66
+ // await queueStore.fetchGroups(forceRefresh)
67
+ // await queueStore.fetchBatches(forceRefresh)
43
68
}
44
69
catch (err ) {
45
70
error .value = ' Failed to load dashboard data'
46
71
console .error (' Dashboard data loading error:' , err )
47
72
}
48
- })
73
+ finally {
74
+ isLoading .value = false
75
+ }
76
+ }
49
77
50
78
async function refreshData() {
51
- error .value = null
52
- try {
53
- await Promise .all ([
54
- queueStore .fetchQueueStats (true ),
55
- queueStore .fetchQueues (true ),
56
- ])
57
-
58
- // Generate new processing rate data
59
- processingRateData .value = Array .from ({ length: 18 }, () =>
60
- Math .max (20 , Math .floor (Math .random () * queueStore .stats .processingRate * 1.5 )))
61
- }
62
- catch (err ) {
63
- error .value = ' Failed to refresh dashboard data'
64
- console .error (' Dashboard data refresh error:' , err )
65
- }
79
+ await fetchDashboardData (true )
66
80
}
67
81
</script >
68
82
@@ -75,14 +89,18 @@ async function refreshData() {
75
89
Dashboard Overview
76
90
</h2 >
77
91
</div >
78
- <button class =" btn btn-primary flex items-center" :disabled =" queueStore.isLoadingStats || queueStore.isLoadingQueues" @click =" refreshData" >
79
- <span v-if =" queueStore.isLoadingStats || queueStore.isLoadingQueues" class =" loader mr-2" />
92
+ <button
93
+ class =" btn btn-primary flex items-center"
94
+ :disabled =" isLoading || queueStore.isLoadingStats || queueStore.isLoadingQueues"
95
+ @click =" refreshData"
96
+ >
97
+ <span v-if =" isLoading || queueStore.isLoadingStats || queueStore.isLoadingQueues" class =" loader mr-2" />
80
98
<span v-else class =" i-carbon-refresh mr-2" />
81
- {{ queueStore.isLoadingStats || queueStore.isLoadingQueues ? 'Loading...' : 'Refresh' }}
99
+ {{ isLoading || queueStore.isLoadingStats || queueStore.isLoadingQueues ? 'Loading...' : 'Refresh' }}
82
100
</button >
83
101
</div >
84
102
85
- <div v-if =" queueStore.isLoadingStats && !queueStore.hasStats" class =" card p-8 text-center bg-white rounded-xl shadow-md" >
103
+ <div v-if =" isLoading && !queueStore.hasStats" class =" card p-8 text-center bg-white rounded-xl shadow-md" >
86
104
<div class =" flex justify-center items-center space-x-3" >
87
105
<div class =" w-5 h-5 rounded-full bg-indigo-600 animate-pulse" />
88
106
<div class =" w-5 h-5 rounded-full bg-indigo-600 animate-pulse" style =" animation-delay : 0.2s " />
@@ -182,7 +200,7 @@ async function refreshData() {
182
200
</h3 >
183
201
</div >
184
202
<div class =" p-2 h-64 bg-white rounded-lg" >
185
- <div v-if =" queueStore.isLoadingStats && !queueStore.hasStats" class =" flex h-full items-center justify-center" >
203
+ <div v-if =" isLoading && !queueStore.hasStats" class =" flex h-full items-center justify-center" >
186
204
<div class =" loader mr-2" />
187
205
<span class =" text-gray-500" >Loading chart data...</span >
188
206
</div >
@@ -217,7 +235,7 @@ async function refreshData() {
217
235
</h3 >
218
236
</div >
219
237
<div class =" p-2 h-64 bg-white rounded-lg flex justify-center items-center" >
220
- <div v-if =" queueStore.isLoadingStats && !queueStore.hasStats" class =" flex items-center" >
238
+ <div v-if =" isLoading && !queueStore.hasStats" class =" flex items-center" >
221
239
<div class =" loader mr-2" />
222
240
<span class =" text-gray-500" >Loading chart data...</span >
223
241
</div >
@@ -278,6 +296,155 @@ async function refreshData() {
278
296
</div >
279
297
</div >
280
298
299
+ <!-- Group Activity Section -->
300
+ <div class =" card p-5 rounded-xl shadow hover:shadow-lg transition-shadow mb-8" >
301
+ <div class =" flex items-center mb-6" >
302
+ <span class =" i-carbon-group text-xl text-indigo-600 mr-2" />
303
+ <h3 class =" text-lg font-medium text-gray-800" >
304
+ Job Groups
305
+ </h3 >
306
+ </div >
307
+
308
+ <div v-if =" isLoading" class =" py-8 text-center" >
309
+ <div class =" loader mx-auto mb-4" />
310
+ <p class =" text-gray-500" >
311
+ Loading group data...
312
+ </p >
313
+ </div >
314
+
315
+ <div v-else-if =" groupData.length === 0" class =" py-8 text-center text-gray-500" >
316
+ No group data available
317
+ </div >
318
+
319
+ <div v-else >
320
+ <div class =" grid grid-cols-1 md:grid-cols-3 gap-4 mb-4" >
321
+ <div v-for =" group in groupData" :key =" group.name" class =" p-4 bg-white rounded-lg border border-gray-100 shadow-sm" >
322
+ <div class =" flex justify-between items-start mb-2" >
323
+ <h4 class =" font-medium text-gray-800" >
324
+ {{ group.name }}
325
+ </h4 >
326
+ <span class =" text-xs px-2 py-1 rounded bg-blue-100 text-blue-700" >{{ group.activeJobs }} active</span >
327
+ </div >
328
+
329
+ <div class =" mt-3" >
330
+ <div class =" flex justify-between text-xs mb-1" >
331
+ <span >Completion</span >
332
+ <span >{{ group.completionRate }}%</span >
333
+ </div >
334
+ <div class =" h-1.5 w-full bg-gray-200 rounded-full overflow-hidden" >
335
+ <div
336
+ class =" h-full bg-indigo-500 rounded-full"
337
+ :style =" { width: `${group.completionRate}%` }"
338
+ />
339
+ </div >
340
+ </div >
341
+
342
+ <div class =" mt-3 text-sm text-gray-600" >
343
+ {{ group.jobCount }} total jobs
344
+ </div >
345
+ </div >
346
+ </div >
347
+
348
+ <div class =" text-right mt-4" >
349
+ <router-link to =" /groups" class =" btn btn-outline text-sm" >
350
+ <span class =" i-carbon-list-checked mr-2" />
351
+ View All Groups
352
+ </router-link >
353
+ </div >
354
+ </div >
355
+ </div >
356
+
357
+ <!-- Recent Batches Section -->
358
+ <div class =" card p-5 rounded-xl shadow hover:shadow-lg transition-shadow mb-8" >
359
+ <div class =" flex items-center mb-6" >
360
+ <span class =" i-carbon-batch-job text-xl text-indigo-600 mr-2" />
361
+ <h3 class =" text-lg font-medium text-gray-800" >
362
+ Recent Batches
363
+ </h3 >
364
+ </div >
365
+
366
+ <div v-if =" isLoading" class =" py-8 text-center" >
367
+ <div class =" loader mx-auto mb-4" />
368
+ <p class =" text-gray-500" >
369
+ Loading batch data...
370
+ </p >
371
+ </div >
372
+
373
+ <div v-else-if =" recentBatches.length === 0" class =" py-8 text-center text-gray-500" >
374
+ No batch data available
375
+ </div >
376
+
377
+ <div v-else >
378
+ <div class =" overflow-x-auto" >
379
+ <table class =" w-full" >
380
+ <thead >
381
+ <tr class =" text-left border-b border-gray-200" >
382
+ <th class =" pb-3 font-medium text-gray-500" >
383
+ Batch Name
384
+ </th >
385
+ <th class =" pb-3 font-medium text-gray-500" >
386
+ Status
387
+ </th >
388
+ <th class =" pb-3 font-medium text-gray-500" >
389
+ Progress
390
+ </th >
391
+ <th class =" pb-3 font-medium text-gray-500 text-right" >
392
+ Jobs
393
+ </th >
394
+ </tr >
395
+ </thead >
396
+ <tbody >
397
+ <tr v-for =" batch in recentBatches" :key =" batch.id" class =" border-b border-gray-100" >
398
+ <td class =" py-3" >
399
+ <div class =" font-medium text-gray-800" >
400
+ {{ batch.name }}
401
+ </div >
402
+ <div class =" text-xs text-gray-500" >
403
+ {{ batch.id }}
404
+ </div >
405
+ </td >
406
+ <td class =" py-3" >
407
+ <span
408
+ class =" px-2 py-1 text-xs rounded-full"
409
+ :class =" {
410
+ 'bg-blue-100 text-blue-700': batch.status === 'active',
411
+ 'bg-emerald-100 text-emerald-700': batch.status === 'completed',
412
+ 'bg-amber-100 text-amber-700': batch.status === 'waiting',
413
+ 'bg-red-100 text-red-700': batch.status === 'failed',
414
+ }"
415
+ >
416
+ {{ batch.status }}
417
+ </span >
418
+ </td >
419
+ <td class =" py-3" >
420
+ <div class =" flex items-center" >
421
+ <div class =" w-32 bg-gray-200 rounded-full h-2 mr-3" >
422
+ <div
423
+ class =" bg-indigo-500 h-2 rounded-full"
424
+ :style =" { width: `${(batch.completedJobs / batch.jobCount) * 100}%` }"
425
+ />
426
+ </div >
427
+ <span class =" text-sm" >{{ Math.round((batch.completedJobs / batch.jobCount) * 100) }}%</span >
428
+ </div >
429
+ </td >
430
+ <td class =" py-3 text-right" >
431
+ <span class =" font-medium" >{{ batch.completedJobs }}</span >
432
+ <span class =" text-gray-500" >/{{ batch.jobCount }}</span >
433
+ </td >
434
+ </tr >
435
+ </tbody >
436
+ </table >
437
+ </div >
438
+
439
+ <div class =" text-right mt-4" >
440
+ <router-link to =" /batches" class =" btn btn-outline text-sm" >
441
+ <span class =" i-carbon-list mr-2" />
442
+ View All Batches
443
+ </router-link >
444
+ </div >
445
+ </div >
446
+ </div >
447
+
281
448
<!-- Queue Activity -->
282
449
<div class =" card p-5 rounded-xl shadow hover:shadow-lg transition-shadow" >
283
450
<div class =" flex items-center mb-6" >
@@ -287,7 +454,7 @@ async function refreshData() {
287
454
</h3 >
288
455
</div >
289
456
290
- <div v-if =" queueStore.isLoadingQueues && !queueStore.hasQueues" class =" py-8 text-center" >
457
+ <div v-if =" isLoading && !queueStore.hasQueues" class =" py-8 text-center" >
291
458
<div class =" loader mx-auto mb-4" />
292
459
<p class =" text-gray-500" >
293
460
Loading queue data...
0 commit comments