sozsoft-platform/ui/src/views/forum/admin/Dashboard.tsx

162 lines
5.3 KiB
TypeScript
Raw Normal View History

import { useMemo } from 'react'
2026-05-13 20:03:10 +00:00
import Widget from '@/components/ui/Widget/Widget'
2026-02-24 20:44:16 +00:00
import { ForumCategory, ForumPost, ForumTopic } from '@/proxy/forum/forum'
import { useLocalization } from '@/utils/hooks/useLocalization'
import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime'
import 'dayjs/locale/tr'
// Plugin kaydı modül seviyesinde yapılır; her render'da tekrarlanması gereksiz iştir.
dayjs.extend(relativeTime)
const MAX_ACTIVITIES = 5
2026-02-24 20:44:16 +00:00
interface AdminStatsProps {
categories: ForumCategory[]
topics: ForumTopic[]
posts: ForumPost[]
2026-07-11 20:35:32 +00:00
totalCounts: { categories: number; topics: number; posts: number }
2026-02-24 20:44:16 +00:00
}
interface Activity {
id: string
2026-02-24 20:44:16 +00:00
message: string
color: string
date: Date
}
2026-07-11 20:35:32 +00:00
export function AdminStats({ categories, topics, posts, totalCounts }: AdminStatsProps) {
const { translate, cultureName } = useLocalization()
2026-02-24 20:44:16 +00:00
const activeCategories = categories.filter((c) => c.isActive).length
const solvedTopics = topics.filter((t) => t.isSolved).length
const acceptedAnswers = posts.filter((p) => p.isAcceptedAnswer).length
const stats = [
{
key: 'categories',
title: translate('::App.ForumDashboard.TotalCategories'),
value: totalCounts.categories,
subTitle: translate('::App.ForumDashboard.ActiveCategories', { 0: activeCategories }),
2026-05-13 20:03:10 +00:00
icon: 'FaFolder' as const,
color: 'blue' as const,
2026-02-24 20:44:16 +00:00
},
{
key: 'topics',
title: translate('::App.ForumDashboard.TotalTopics'),
value: totalCounts.topics,
subTitle: translate('::App.ForumDashboard.SolvedTopics', { 0: solvedTopics }),
2026-05-13 20:03:10 +00:00
icon: 'FaCommentDots' as const,
color: 'green' as const,
2026-02-24 20:44:16 +00:00
},
{
key: 'posts',
title: translate('::App.ForumDashboard.TotalPosts'),
value: totalCounts.posts,
subTitle: translate('::App.ForumDashboard.AcceptedAnswers', { 0: acceptedAnswers }),
2026-05-13 20:03:10 +00:00
icon: 'FaFileAlt' as const,
color: 'orange' as const,
2026-02-24 20:44:16 +00:00
},
{
key: 'engagement',
title: translate('::App.ForumDashboard.EngagementRate'),
value:
totalCounts.topics > 0
? Math.round((totalCounts.posts / totalCounts.topics) * 100) / 100
: 0,
subTitle: translate('::App.ForumDashboard.PostsPerTopic'),
2026-05-13 20:03:10 +00:00
icon: 'FaChartLine' as const,
color: 'purple' as const,
2026-02-24 20:44:16 +00:00
},
]
const latestActivities = useMemo<Activity[]>(() => {
const categoryNameById = new Map(categories.map((c) => [c.id, c.name]))
const activities: Activity[] = []
2026-02-24 20:44:16 +00:00
topics.forEach((topic) => {
if (!topic.creationTime) return
activities.push({
id: `topic-${topic.id}`,
message: `${translate('::App.ForumTopicManagement.NewTopic')}${
categoryNameById.get(topic.categoryId) ?? translate('::App.Platform.Unknown')
}`,
2026-02-24 20:44:16 +00:00
color: 'bg-blue-500',
date: new Date(topic.creationTime),
})
})
2026-02-24 20:44:16 +00:00
posts.forEach((post) => {
if (!post.isAcceptedAnswer || !post.creationTime) return
activities.push({
id: `post-${post.id}`,
message: translate('::App.ForumDashboard.Postmarked'),
2026-02-24 20:44:16 +00:00
color: 'bg-emerald-500',
date: new Date(post.creationTime),
})
})
2026-02-24 20:44:16 +00:00
categories.forEach((category) => {
if (!category.creationTime) return
activities.push({
id: `category-${category.id}`,
message: `${translate('::App.ForumDashboard.NewCategoryCreated')}${category.name}`,
2026-02-24 20:44:16 +00:00
color: 'bg-orange-500',
date: new Date(category.creationTime),
})
})
2026-02-24 20:44:16 +00:00
return activities
.sort((a, b) => b.date.getTime() - a.date.getTime())
.slice(0, MAX_ACTIVITIES)
}, [categories, posts, topics, translate])
2026-02-24 20:44:16 +00:00
return (
2026-05-13 20:03:10 +00:00
<div className="space-y-4">
<div className="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-4">
{stats.map((stat) => (
2026-05-13 20:03:10 +00:00
<Widget
key={stat.key}
2026-05-13 20:03:10 +00:00
title={stat.title}
value={stat.value}
subTitle={stat.subTitle}
icon={stat.icon}
color={stat.color}
/>
))}
2026-02-24 20:44:16 +00:00
</div>
{/* Recent Activity */}
<div className="rounded-xl border border-gray-200 bg-white p-6 shadow-sm dark:border-gray-700 dark:bg-gray-900">
<h3 className="mb-4 text-lg font-semibold text-gray-900 dark:text-white">
{translate('::App.ForumDashboard.RecentActivity')}
2026-02-24 20:44:16 +00:00
</h3>
<div className="space-y-4">
{latestActivities.length === 0 ? (
<p className="text-sm text-gray-500 dark:text-gray-400">
{translate('::App.Platform.NoDataFound')}
</p>
) : (
latestActivities.map((activity) => (
<div
key={activity.id}
className="flex items-start space-x-3 rounded-lg bg-gray-50 p-3 dark:bg-gray-800"
>
<div className={`mt-2 h-2 w-2 ${activity.color} rounded-full`} />
<div>
<p className="text-sm text-gray-900 dark:text-white">{activity.message}</p>
<p className="text-xs text-gray-500 dark:text-gray-400">
{dayjs(activity.date)
.locale(cultureName?.startsWith('tr') ? 'tr' : 'en')
.fromNow()}
</p>
</div>
2026-02-24 20:44:16 +00:00
</div>
))
)}
2026-02-24 20:44:16 +00:00
</div>
</div>
</div>
)
}