sozsoft-platform/ui/src/views/forum/admin/Dashboard.tsx
2026-08-15 08:57:47 +03:00

161 lines
5.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useMemo } from 'react'
import Widget from '@/components/ui/Widget/Widget'
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
interface AdminStatsProps {
categories: ForumCategory[]
topics: ForumTopic[]
posts: ForumPost[]
totalCounts: { categories: number; topics: number; posts: number }
}
interface Activity {
id: string
message: string
color: string
date: Date
}
export function AdminStats({ categories, topics, posts, totalCounts }: AdminStatsProps) {
const { translate, cultureName } = useLocalization()
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 }),
icon: 'FaFolder' as const,
color: 'blue' as const,
},
{
key: 'topics',
title: translate('::App.ForumDashboard.TotalTopics'),
value: totalCounts.topics,
subTitle: translate('::App.ForumDashboard.SolvedTopics', { 0: solvedTopics }),
icon: 'FaCommentDots' as const,
color: 'green' as const,
},
{
key: 'posts',
title: translate('::App.ForumDashboard.TotalPosts'),
value: totalCounts.posts,
subTitle: translate('::App.ForumDashboard.AcceptedAnswers', { 0: acceptedAnswers }),
icon: 'FaFileAlt' as const,
color: 'orange' as const,
},
{
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'),
icon: 'FaChartLine' as const,
color: 'purple' as const,
},
]
const latestActivities = useMemo<Activity[]>(() => {
const categoryNameById = new Map(categories.map((c) => [c.id, c.name]))
const activities: Activity[] = []
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')
}`,
color: 'bg-blue-500',
date: new Date(topic.creationTime),
})
})
posts.forEach((post) => {
if (!post.isAcceptedAnswer || !post.creationTime) return
activities.push({
id: `post-${post.id}`,
message: translate('::App.ForumDashboard.Postmarked'),
color: 'bg-emerald-500',
date: new Date(post.creationTime),
})
})
categories.forEach((category) => {
if (!category.creationTime) return
activities.push({
id: `category-${category.id}`,
message: `${translate('::App.ForumDashboard.NewCategoryCreated')}${category.name}`,
color: 'bg-orange-500',
date: new Date(category.creationTime),
})
})
return activities
.sort((a, b) => b.date.getTime() - a.date.getTime())
.slice(0, MAX_ACTIVITIES)
}, [categories, posts, topics, translate])
return (
<div className="space-y-4">
<div className="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-4">
{stats.map((stat) => (
<Widget
key={stat.key}
title={stat.title}
value={stat.value}
subTitle={stat.subTitle}
icon={stat.icon}
color={stat.color}
/>
))}
</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')}
</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>
</div>
))
)}
</div>
</div>
</div>
)
}