diff --git a/src/views/AdminDashboard.tsx b/src/views/AdminDashboard.tsx
index 12fb2f6..63c2526 100644
--- a/src/views/AdminDashboard.tsx
+++ b/src/views/AdminDashboard.tsx
@@ -30,11 +30,8 @@ import {
Cell,
Line,
LineChart,
- Scatter,
- ScatterChart,
XAxis,
YAxis,
- ZAxis,
} from "recharts";
import AsyncSearchableCombobox from "@/components/AsyncSearchableCombobox";
import { Badge } from "@/components/ui/badge";
@@ -436,6 +433,167 @@ function DashboardPointDetailModal({
);
}
+type PostEngagementDatum = AnalyticsPostPopularitySchema & {
+ label: string;
+ score: number;
+};
+
+function postScore(post: AnalyticsPostPopularitySchema) {
+ return Number(post.likes || 0) + Number(post.saves || 0) + Number(post.comments || 0);
+}
+
+function toPostEngagementData(posts: AnalyticsPostPopularitySchema[]): PostEngagementDatum[] {
+ return posts.map((post) => ({
+ ...post,
+ label: post.title,
+ score: postScore(post),
+ }));
+}
+
+function PostEngagementTooltip({
+ active,
+ payload,
+}: {
+ active?: boolean;
+ payload?: Array<{ payload?: PostEngagementDatum }>;
+}) {
+ if (!active || !payload?.length || !payload[0].payload) return null;
+ const item = payload[0].payload;
+ return (
+
+
{item.title}
+
+
لایک: {formatNumberPersian(item.likes)}
+
ذخیره: {formatNumberPersian(item.saves)}
+
کامنت: {formatNumberPersian(item.comments)}
+
تعامل کل: {formatNumberPersian(item.score)}
+
+
+ );
+}
+
+function BlogPostEngagementModal({
+ open,
+ onOpenChange,
+ posts,
+}: {
+ open: boolean;
+ onOpenChange: (open: boolean) => void;
+ posts: AnalyticsPostPopularitySchema[];
+}) {
+ const [search, setSearch] = React.useState("");
+ const [sortBy, setSortBy] = React.useState<"score" | "likes" | "saves" | "comments" | "title">("score");
+ const data = React.useMemo(() => {
+ const normalizedSearch = search.trim().toLocaleLowerCase("fa-IR");
+ const filtered = normalizedSearch
+ ? posts.filter((post) => post.title.toLocaleLowerCase("fa-IR").includes(normalizedSearch))
+ : posts;
+ return toPostEngagementData(filtered).sort((a, b) => {
+ if (sortBy === "title") return a.title.localeCompare(b.title, "fa");
+ return Number(b[sortBy]) - Number(a[sortBy]) || a.title.localeCompare(b.title, "fa");
+ });
+ }, [posts, search, sortBy]);
+
+ return (
+
+ );
+}
+
function ValuesTable({
data,
unit,
@@ -716,13 +874,16 @@ function ActivityTrendCard({ data }: { data: BlogAnalyticsSchema["activity_trend
);
}
-function BlogScatterCard({ group }: { group: BlogAnalyticsSchema["post_popularity"] }) {
- const data = group.top_items;
+function BlogEngagementCard({ group }: { group: BlogAnalyticsSchema["post_popularity"] }) {
+ const data = toPostEngagementData(group.top_items);
+ const allPosts = group.items?.length ? group.items : group.top_items;
+ const [detailsOpen, setDetailsOpen] = React.useState(false);
+ const labelAxisWidth = axisWidth(data.map((post) => ({ label: post.title, value: post.score })));
return (
محبوبیت نوشتهها
- لایک در برابر ذخیره؛ اندازه نقطه بر اساس تعداد کامنت
+ رتبهبندی بر اساس مجموع لایک، ذخیره و کامنت
{!data.length ? (
@@ -732,52 +893,48 @@ function BlogScatterCard({ group }: { group: BlogAnalyticsSchema["post_popularit
-
-
+
+
formatNumberPersian(Number(value))}
/>
formatNumberPersian(Number(value))}
+ dataKey="label"
+ type="category"
+ orientation="right"
+ width={labelAxisWidth}
+ tickLine={false}
+ axisLine={false}
+ tickMargin={10}
+ tickFormatter={(value) => truncateLabel(String(value), 22)}
/>
-
- {
- if (!active || !payload?.length) return null;
- const item = payload[0].payload as AnalyticsPostPopularitySchema;
- return (
-
-
{item.title}
-
-
لایک: {formatNumberPersian(item.likes)}
-
ذخیره: {formatNumberPersian(item.saves)}
-
کامنت: {formatNumberPersian(item.comments)}
-
-
- );
- }}
- />
-
-
+ } />
+
+
+
+
{group.total_count > data.length ? (
-
- نمایش {formatNumberPersian(data.length)} نوشته برتر از {formatNumberPersian(group.total_count)} نوشته دارای تعامل.
-
+
+ نمودار خلاصه {formatNumberPersian(data.length)} نوشته اول را نشان میدهد.
+
+
) : null}
+
>
)}
@@ -1033,7 +1190,7 @@ function BlogContent({ data }: { data: BlogAnalyticsSchema }) {