diff --git a/src/views/AdminDashboard.tsx b/src/views/AdminDashboard.tsx
index f421ded..374031e 100644
--- a/src/views/AdminDashboard.tsx
+++ b/src/views/AdminDashboard.tsx
@@ -42,6 +42,7 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/u
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
+import { useIsMobile } from "@/hooks/use-mobile";
import { api } from "@/lib/api";
import type {
AnalyticsPointGroupSchema,
@@ -138,13 +139,13 @@ function truncateLabel(value: string, max = 18) {
return normalized.length > max ? `${normalized.slice(0, max - 1)}…` : normalized;
}
-function chartHeight(count: number, min = 280) {
- return Math.max(min, count * 32 + 80);
+function chartHeight(count: number, min = 280, compact = false) {
+ return Math.max(compact ? Math.min(min, 240) : min, count * (compact ? 26 : 32) + (compact ? 64 : 80));
}
-function axisWidth(items: AnalyticsPointSchema[]) {
+function axisWidth(items: AnalyticsPointSchema[], compact = false) {
const maxLength = Math.max(...items.map((item) => String(item.label).length), 10);
- return Math.min(190, Math.max(90, maxLength * 7));
+ return Math.min(compact ? 92 : 190, Math.max(compact ? 64 : 90, maxLength * (compact ? 4.5 : 7)));
}
function dataWithOtherNotice(group: AnalyticsPointGroupSchema) {
@@ -280,10 +281,23 @@ function FilterCard({
);
}
-function ChartViewport({ children, minWidth = 420 }: { children: React.ReactNode; minWidth?: number }) {
+function ChartViewport({
+ children,
+ minWidth = 420,
+ scrollable = false,
+}: {
+ children: React.ReactNode;
+ minWidth?: number;
+ scrollable?: boolean;
+}) {
+ const isMobile = useIsMobile();
+ const shouldScroll = scrollable && !isMobile;
+
return (
-
-
{children}
+
);
}
@@ -354,6 +368,7 @@ function DashboardPointDetailModal({
}) {
const [search, setSearch] = React.useState("");
const [sortBy, setSortBy] = React.useState<"value" | "label">("value");
+ const isMobile = useIsMobile();
const filteredData = React.useMemo(() => {
const normalizedSearch = search.trim().toLocaleLowerCase("fa-IR");
const filtered = normalizedSearch
@@ -390,16 +405,16 @@ function DashboardPointDetailModal({
) : (
<>
-
+
truncateLabel(String(value), 24)}
+ tickMargin={isMobile ? 4 : 10}
+ tickFormatter={(value) => truncateLabel(String(value), isMobile ? 12 : 24)}
/>
} />
@@ -484,6 +499,7 @@ function BlogPostEngagementModal({
}) {
const [search, setSearch] = React.useState("");
const [sortBy, setSortBy] = React.useState<"score" | "likes" | "saves" | "comments" | "title">("score");
+ const isMobile = useIsMobile();
const data = React.useMemo(() => {
const normalizedSearch = search.trim().toLocaleLowerCase("fa-IR");
const filtered = normalizedSearch
@@ -530,7 +546,7 @@ function BlogPostEngagementModal({
) : (
<>
-
+
({ label: post.title, value: post.score })))}
+ width={axisWidth(data.map((post) => ({ label: post.title, value: post.score })), isMobile)}
tickLine={false}
axisLine={false}
- tickMargin={10}
- tickFormatter={(value) => truncateLabel(String(value), 26)}
+ tickMargin={isMobile ? 4 : 10}
+ tickFormatter={(value) => truncateLabel(String(value), isMobile ? 12 : 26)}
/>
} />
@@ -651,8 +667,9 @@ function HorizontalBarCard({
const data = dataWithOtherNotice(group);
const allData = fullGroupItems(group);
const [detailsOpen, setDetailsOpen] = React.useState(false);
+ const isMobile = useIsMobile();
return (
-
+
{title}
{description}
@@ -666,9 +683,13 @@ function HorizontalBarCard({
-
+
truncateLabel(String(value))}
+ tickMargin={isMobile ? 4 : 10}
+ tickFormatter={(value) => truncateLabel(String(value), isMobile ? 10 : 18)}
/>
} />
@@ -726,8 +747,9 @@ function TrendLineCard({
color?: string;
valueFormatter?: (value: number) => string;
}) {
+ const isMobile = useIsMobile();
return (
-
+
@@ -740,24 +762,27 @@ function TrendLineCard({
) : (
-
-
+
+
valueFormatter(Number(value))}
/>
;
}) {
+ const isMobile = useIsMobile();
return (
-
+
{title}
{description}
@@ -802,9 +828,13 @@ function StatusChartCard({
) : (
<>
-
-
-
+
+
+
truncateLabel(String(value), 14)}
+ tickMargin={isMobile ? 4 : 10}
+ tickFormatter={(value) => truncateLabel(String(value), isMobile ? 9 : 14)}
/>
} />
@@ -843,8 +873,9 @@ function StatusChartCard({
}
function ActivityTrendCard({ data }: { data: BlogAnalyticsSchema["activity_trend"] }) {
+ const isMobile = useIsMobile();
return (
-
+
روند تعاملات بلاگ
لایک، ذخیره و کامنت در بازه انتخابی
@@ -860,17 +891,20 @@ function ActivityTrendCard({ data }: { data: BlogAnalyticsSchema["activity_trend
saves: { label: "ذخیره", color: PALETTE.cyan },
comments: { label: "کامنت", color: PALETTE.amber },
}}
- className="h-[280px] w-full sm:h-[320px]"
+ className="h-[230px] w-full sm:h-[320px]"
>
-
+
-
+
formatNumberPersian(Number(value))}
/>
} />
@@ -890,9 +924,10 @@ function BlogEngagementCard({ group }: { group: BlogAnalyticsSchema["post_popula
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 })));
+ const isMobile = useIsMobile();
+ const labelAxisWidth = axisWidth(data.map((post) => ({ label: post.title, value: post.score })), isMobile);
return (
-
+
محبوبیت نوشتهها
رتبهبندی بر اساس مجموع لایک، ذخیره و کامنت
@@ -902,7 +937,7 @@ function BlogEngagementCard({ group }: { group: BlogAnalyticsSchema["post_popula
) : (
<>
-
+
-
+
truncateLabel(String(value), 22)}
+ tickMargin={isMobile ? 4 : 10}
+ tickFormatter={(value) => truncateLabel(String(value), isMobile ? 10 : 22)}
/>
} />