Skip to content

Commit d15a4dd

Browse files
committed
chore: wip
1 parent 5d6ebc1 commit d15a4dd

File tree

1 file changed

+289
-1
lines changed
  • storage/framework/defaults/views/dashboard/blog/categories

1 file changed

+289
-1
lines changed

storage/framework/defaults/views/dashboard/blog/categories/index.vue

Lines changed: 289 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,158 @@
11
<script lang="ts" setup>
22
import { ref, computed } from 'vue'
33
import { useHead } from '@vueuse/head'
4+
import { Line, Bar, Doughnut } from 'vue-chartjs'
5+
import {
6+
Chart as ChartJS,
7+
CategoryScale,
8+
LinearScale,
9+
PointElement,
10+
LineElement,
11+
BarElement,
12+
ArcElement,
13+
Title,
14+
Tooltip,
15+
Legend,
16+
Filler,
17+
} from 'chart.js'
18+
19+
ChartJS.register(
20+
CategoryScale,
21+
LinearScale,
22+
PointElement,
23+
LineElement,
24+
BarElement,
25+
ArcElement,
26+
Title,
27+
Tooltip,
28+
Legend,
29+
Filler,
30+
)
431
532
useHead({
633
title: 'Dashboard - Blog Categories',
734
})
835
36+
// Chart options
37+
const chartOptions = {
38+
responsive: true,
39+
maintainAspectRatio: false,
40+
plugins: {
41+
legend: {
42+
display: false,
43+
},
44+
},
45+
scales: {
46+
y: {
47+
beginAtZero: true,
48+
grid: {
49+
display: true,
50+
color: 'rgba(0, 0, 0, 0.05)',
51+
},
52+
},
53+
x: {
54+
grid: {
55+
display: false,
56+
},
57+
},
58+
},
59+
}
60+
61+
// Doughnut chart options (no scales)
62+
const doughnutChartOptions = {
63+
responsive: true,
64+
maintainAspectRatio: false,
65+
plugins: {
66+
legend: {
67+
display: true,
68+
position: 'bottom' as const,
69+
},
70+
},
71+
}
72+
73+
// Generate monthly data for charts
74+
const monthlyChartData = computed(() => {
75+
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
76+
77+
// Sample data - in a real app, this would be calculated from actual category data
78+
const categoryGrowthData = [5, 6, 7, 8, 8, 9, 10, 10, 11, 11, 12, 12]
79+
80+
// Category growth chart data
81+
const categoryGrowthChartData = {
82+
labels: months,
83+
datasets: [
84+
{
85+
label: 'Categories',
86+
backgroundColor: 'rgba(59, 130, 246, 0.2)',
87+
borderColor: 'rgba(59, 130, 246, 1)',
88+
borderWidth: 2,
89+
pointBackgroundColor: 'rgba(59, 130, 246, 1)',
90+
pointBorderColor: '#fff',
91+
pointHoverBackgroundColor: '#fff',
92+
pointHoverBorderColor: 'rgba(59, 130, 246, 1)',
93+
fill: true,
94+
tension: 0.4,
95+
data: categoryGrowthData,
96+
},
97+
],
98+
}
99+
100+
// Posts per category chart data
101+
const postCountData = categories.value.map(category => category.postCount)
102+
const categoryNames = categories.value.map(category => category.name)
103+
104+
const postsPerCategoryChartData = {
105+
labels: categoryNames,
106+
datasets: [
107+
{
108+
label: 'Posts',
109+
backgroundColor: 'rgba(16, 185, 129, 0.8)',
110+
borderColor: 'rgba(16, 185, 129, 1)',
111+
borderWidth: 1,
112+
borderRadius: 4,
113+
data: postCountData,
114+
},
115+
],
116+
}
117+
118+
// Category distribution chart data
119+
const backgroundColors = [
120+
'rgba(59, 130, 246, 0.8)',
121+
'rgba(16, 185, 129, 0.8)',
122+
'rgba(245, 158, 11, 0.8)',
123+
'rgba(239, 68, 68, 0.8)',
124+
'rgba(139, 92, 246, 0.8)',
125+
'rgba(236, 72, 153, 0.8)',
126+
'rgba(75, 85, 99, 0.8)',
127+
'rgba(14, 165, 233, 0.8)',
128+
'rgba(168, 85, 247, 0.8)',
129+
'rgba(249, 115, 22, 0.8)',
130+
'rgba(234, 88, 12, 0.8)',
131+
'rgba(217, 119, 6, 0.8)',
132+
]
133+
134+
const categoryDistributionChartData = {
135+
labels: categoryNames,
136+
datasets: [
137+
{
138+
data: postCountData,
139+
backgroundColor: backgroundColors.slice(0, categoryNames.length),
140+
borderWidth: 0,
141+
},
142+
],
143+
}
144+
145+
return {
146+
categoryGrowthChartData,
147+
postsPerCategoryChartData,
148+
categoryDistributionChartData,
149+
}
150+
})
151+
152+
// Time range selector
153+
const timeRange = ref('Last 30 days')
154+
const timeRanges = ['Today', 'Last 7 days', 'Last 30 days', 'Last 90 days', 'Last year', 'All time']
155+
9156
// Define category type
10157
interface Category {
11158
id: number
@@ -204,6 +351,62 @@ const paginationRange = computed(() => {
204351
return range
205352
})
206353
354+
// Computed category statistics
355+
const categoryStats = computed(() => {
356+
// Total number of categories
357+
const totalCategories = categories.value.length
358+
359+
// Total number of posts across all categories
360+
const totalPosts = categories.value.reduce((sum, category) => sum + category.postCount, 0)
361+
362+
// Average posts per category
363+
const avgPostsPerCategory = totalCategories > 0
364+
? (totalPosts / totalCategories).toFixed(1)
365+
: '0.0'
366+
367+
// Find category with most posts
368+
let mostPopularCategory = categories.value[0] || { name: 'None', postCount: 0 }
369+
370+
for (const category of categories.value) {
371+
if (category.postCount > mostPopularCategory.postCount) {
372+
mostPopularCategory = category
373+
}
374+
}
375+
376+
// Find category with least posts
377+
let leastPopularCategory = categories.value[0] || { name: 'None', postCount: 0 }
378+
379+
for (const category of categories.value) {
380+
if (category.postCount < leastPopularCategory.postCount) {
381+
leastPopularCategory = category
382+
}
383+
}
384+
385+
// Calculate percentage of posts in top category
386+
const topCategoryPercentage = totalPosts > 0
387+
? ((mostPopularCategory.postCount / totalPosts) * 100).toFixed(1)
388+
: '0.0'
389+
390+
// Find newest category
391+
let newestCategory = categories.value[0] || { name: 'None', createdAt: '' } as Category
392+
393+
for (const category of categories.value) {
394+
if (new Date(category.createdAt) > new Date(newestCategory.createdAt)) {
395+
newestCategory = category
396+
}
397+
}
398+
399+
return {
400+
totalCategories,
401+
totalPosts,
402+
avgPostsPerCategory,
403+
mostPopularCategory,
404+
leastPopularCategory,
405+
topCategoryPercentage,
406+
newestCategory
407+
}
408+
})
409+
207410
// Methods
208411
function openNewCategoryModal(): void {
209412
newCategory.value = {
@@ -353,8 +556,87 @@ const hasSelectedCategories = computed(() => selectedCategoryIds.value.length >
353556
</div>
354557
</div>
355558

559+
<!-- Time range selector -->
560+
<div class="mt-4 flex items-center justify-between">
561+
<p class="text-sm text-gray-500 dark:text-gray-400">
562+
Overview of your blog categories
563+
</p>
564+
<div class="relative">
565+
<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">
566+
<option v-for="range in timeRanges" :key="range" :value="range">{{ range }}</option>
567+
</select>
568+
</div>
569+
</div>
570+
571+
<!-- Stats -->
572+
<dl class="mt-5 grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-4">
573+
<div class="overflow-hidden rounded-lg bg-white px-4 py-5 shadow sm:p-6 dark:bg-blue-gray-800">
574+
<dt class="truncate text-sm font-medium text-gray-500 dark:text-gray-300">Total Categories</dt>
575+
<dd class="mt-1 text-3xl font-semibold tracking-tight text-gray-900 dark:text-white">{{ categoryStats.totalCategories }}</dd>
576+
<dd class="mt-2 flex items-center text-sm text-green-600 dark:text-green-400">
577+
<div class="i-hugeicons-analytics-up h-4 w-4 mr-1"></div>
578+
<span>{{ categoryStats.newestCategory.name }} added recently</span>
579+
</dd>
580+
</div>
581+
582+
<div class="overflow-hidden rounded-lg bg-white px-4 py-5 shadow sm:p-6 dark:bg-blue-gray-800">
583+
<dt class="truncate text-sm font-medium text-gray-500 dark:text-gray-300">Total Posts</dt>
584+
<dd class="mt-1 text-3xl font-semibold tracking-tight text-gray-900 dark:text-white">{{ categoryStats.totalPosts }}</dd>
585+
<dd class="mt-2 flex items-center text-sm text-gray-500 dark:text-gray-400">
586+
<span>{{ categoryStats.avgPostsPerCategory }} avg per category</span>
587+
</dd>
588+
</div>
589+
590+
<div class="overflow-hidden rounded-lg bg-white px-4 py-5 shadow sm:p-6 dark:bg-blue-gray-800">
591+
<dt class="truncate text-sm font-medium text-gray-500 dark:text-gray-300">Top Category</dt>
592+
<dd class="mt-1 text-3xl font-semibold tracking-tight text-gray-900 dark:text-white">{{ categoryStats.mostPopularCategory.name }}</dd>
593+
<dd class="mt-2 flex items-center text-sm text-green-600 dark:text-green-400">
594+
<div class="i-hugeicons-analytics-up h-4 w-4 mr-1"></div>
595+
<span>{{ categoryStats.mostPopularCategory.postCount }} posts ({{ categoryStats.topCategoryPercentage }}%)</span>
596+
</dd>
597+
</div>
598+
599+
<div class="overflow-hidden rounded-lg bg-white px-4 py-5 shadow sm:p-6 dark:bg-blue-gray-800">
600+
<dt class="truncate text-sm font-medium text-gray-500 dark:text-gray-300">Least Used Category</dt>
601+
<dd class="mt-1 text-3xl font-semibold tracking-tight text-gray-900 dark:text-white">{{ categoryStats.leastPopularCategory.name }}</dd>
602+
<dd class="mt-2 flex items-center text-sm text-gray-500 dark:text-gray-400">
603+
<span>{{ categoryStats.leastPopularCategory.postCount }} posts</span>
604+
</dd>
605+
</div>
606+
</dl>
607+
608+
<!-- Charts -->
609+
<div class="mt-8 grid grid-cols-1 gap-5 lg:grid-cols-3">
610+
<div class="overflow-hidden rounded-lg bg-white shadow dark:bg-blue-gray-800">
611+
<div class="p-6">
612+
<h3 class="text-base font-semibold leading-6 text-gray-900 dark:text-white">Category Growth</h3>
613+
<div class="mt-2 h-80">
614+
<Line :data="monthlyChartData.categoryGrowthChartData" :options="chartOptions" />
615+
</div>
616+
</div>
617+
</div>
618+
619+
<div class="overflow-hidden rounded-lg bg-white shadow dark:bg-blue-gray-800">
620+
<div class="p-6">
621+
<h3 class="text-base font-semibold leading-6 text-gray-900 dark:text-white">Posts per Category</h3>
622+
<div class="mt-2 h-80">
623+
<Bar :data="monthlyChartData.postsPerCategoryChartData" :options="chartOptions" />
624+
</div>
625+
</div>
626+
</div>
627+
628+
<div class="overflow-hidden rounded-lg bg-white shadow dark:bg-blue-gray-800">
629+
<div class="p-6">
630+
<h3 class="text-base font-semibold leading-6 text-gray-900 dark:text-white">Category Distribution</h3>
631+
<div class="mt-2 h-80">
632+
<Doughnut :data="monthlyChartData.categoryDistributionChartData" :options="doughnutChartOptions" />
633+
</div>
634+
</div>
635+
</div>
636+
</div>
637+
356638
<!-- Filters -->
357-
<div class="mt-6 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
639+
<div class="mt-8 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
358640
<div class="relative max-w-sm">
359641
<div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
360642
<div class="i-hugeicons-search-01 h-5 w-5 text-gray-400"></div>
@@ -418,6 +700,12 @@ const hasSelectedCategories = computed(() => selectedCategoryIds.value.length >
418700

419701
<!-- Categories Table -->
420702
<div class="mt-6 flow-root">
703+
<div class="sm:flex sm:items-center sm:justify-between mb-4">
704+
<h3 class="text-base font-semibold leading-6 text-gray-900 dark:text-white">All Categories</h3>
705+
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
706+
A list of all blog categories including name, description, and post count.
707+
</p>
708+
</div>
421709
<div class="overflow-x-auto rounded-lg border border-gray-200 dark:border-gray-700 shadow">
422710
<div class="overflow-hidden">
423711
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">

0 commit comments

Comments
 (0)