fix(admin-dashboard): tighten mobile layout

This commit is contained in:
2026-06-15 17:40:31 +03:30
parent 4fb44fcb4c
commit 4edf8a0736

View File

@@ -139,7 +139,7 @@ function truncateLabel(value: string, max = 18) {
}
function chartHeight(count: number, min = 280) {
return Math.max(min, count * 38 + 90);
return Math.max(min, count * 32 + 80);
}
function axisWidth(items: AnalyticsPointSchema[]) {
@@ -170,14 +170,14 @@ function StatCard({
}) {
return (
<Card className="overflow-hidden">
<CardContent className="flex items-center justify-between gap-4 p-5">
<CardContent className="flex items-center justify-between gap-3 p-4 sm:gap-4 sm:p-5">
<div className="space-y-2 text-right">
<p className="text-sm text-muted-foreground">{title}</p>
<p className="text-2xl font-black leading-tight tracking-tight">{value}</p>
<p className="text-xl font-black leading-tight tracking-tight sm:text-2xl">{value}</p>
<p className="text-xs leading-6 text-muted-foreground">{description}</p>
</div>
<div className="rounded-2xl p-3" style={{ backgroundColor: `${PALETTE[tone]}22`, color: PALETTE[tone] }}>
<Icon className="h-6 w-6" />
<div className="rounded-2xl p-2.5 sm:p-3" style={{ backgroundColor: `${PALETTE[tone]}22`, color: PALETTE[tone] }}>
<Icon className="h-5 w-5 sm:h-6 sm:w-6" />
</div>
</CardContent>
</Card>
@@ -202,7 +202,7 @@ function SectionLoading() {
function EmptyChart({ label = "داده‌ای برای نمایش وجود ندارد." }: { label?: string }) {
return (
<div className="flex h-[240px] items-center justify-center rounded-xl border border-dashed text-sm text-muted-foreground">
<div className="flex h-[200px] items-center justify-center rounded-xl border border-dashed px-4 text-center text-xs text-muted-foreground sm:h-[240px] sm:text-sm">
{label}
</div>
);
@@ -268,19 +268,19 @@ function FilterCard({
}) {
return (
<Card>
<CardHeader>
<CardHeader className="p-4 sm:p-6">
<CardTitle className="flex items-center gap-2">
<BarChart3 className="h-5 w-5 text-primary" />
{title}
</CardTitle>
<CardDescription>{description}</CardDescription>
</CardHeader>
<CardContent>{children}</CardContent>
<CardContent className="p-4 pt-0 sm:p-6 sm:pt-0">{children}</CardContent>
</Card>
);
}
function ChartViewport({ children, minWidth = 560 }: { children: React.ReactNode; minWidth?: number }) {
function ChartViewport({ children, minWidth = 420 }: { children: React.ReactNode; minWidth?: number }) {
return (
<div className="overflow-x-auto overflow-y-hidden pb-2" dir="ltr">
<div style={{ minWidth }}>{children}</div>
@@ -606,7 +606,7 @@ function ValuesTable({
if (!data.length) return null;
return (
<div className="mt-4 max-h-56 overflow-auto rounded-xl border">
<table className="w-full text-sm">
<table className="w-full text-xs sm:text-sm">
<tbody>
{data.map((item, index) => (
<tr key={`${item.label}-${index}`} className="border-b last:border-b-0">
@@ -644,11 +644,11 @@ function HorizontalBarCard({
const [detailsOpen, setDetailsOpen] = React.useState(false);
return (
<Card>
<CardHeader>
<CardTitle>{title}</CardTitle>
<CardHeader className="p-4 sm:p-6">
<CardTitle className="text-base sm:text-lg">{title}</CardTitle>
<CardDescription>{description}</CardDescription>
</CardHeader>
<CardContent>
<CardContent className="p-4 pt-0 sm:p-6 sm:pt-0">
{!data.length ? (
<EmptyChart />
) : (
@@ -718,19 +718,19 @@ function TrendLineCard({
}) {
return (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<CardHeader className="p-4 sm:p-6">
<CardTitle className="flex items-center gap-2 text-base sm:text-lg">
<LineChartIcon className="h-5 w-5 text-primary" />
{title}
</CardTitle>
<CardDescription>{description}</CardDescription>
</CardHeader>
<CardContent>
<CardContent className="p-4 pt-0 sm:p-6 sm:pt-0">
{!data.length ? (
<EmptyChart />
) : (
<ChartViewport>
<ChartContainer config={{ value: { label: "مقدار", color } }} className="h-[300px] w-full">
<ChartContainer config={{ value: { label: "مقدار", color } }} className="h-[260px] w-full sm:h-[300px]">
<LineChart data={data} margin={{ top: 16, right: 76, bottom: 32, left: 18 }}>
<CartesianGrid vertical={false} strokeDasharray="3 3" />
<XAxis
@@ -782,17 +782,17 @@ function StatusChartCard({
}) {
return (
<Card>
<CardHeader>
<CardTitle>{title}</CardTitle>
<CardHeader className="p-4 sm:p-6">
<CardTitle className="text-base sm:text-lg">{title}</CardTitle>
<CardDescription>{description}</CardDescription>
</CardHeader>
<CardContent>
<CardContent className="p-4 pt-0 sm:p-6 sm:pt-0">
{!data.length ? (
<EmptyChart />
) : (
<>
<ChartViewport minWidth={460}>
<ChartContainer config={{ value: { label: "تعداد", color: PALETTE.teal } }} className="h-[300px] w-full">
<ChartViewport minWidth={400}>
<ChartContainer config={{ value: { label: "تعداد", color: PALETTE.teal } }} className="h-[260px] w-full sm:h-[300px]">
<BarChart data={data} layout="vertical" margin={{ top: 14, right: 118, bottom: 28, left: 18 }}>
<CartesianGrid horizontal={false} strokeDasharray="3 3" />
<XAxis
@@ -833,11 +833,11 @@ function StatusChartCard({
function ActivityTrendCard({ data }: { data: BlogAnalyticsSchema["activity_trend"] }) {
return (
<Card>
<CardHeader>
<CardTitle>روند تعاملات بلاگ</CardTitle>
<CardHeader className="p-4 sm:p-6">
<CardTitle className="text-base sm:text-lg">روند تعاملات بلاگ</CardTitle>
<CardDescription>لایک، ذخیره و کامنت در بازه انتخابی</CardDescription>
</CardHeader>
<CardContent>
<CardContent className="p-4 pt-0 sm:p-6 sm:pt-0">
{!data.length ? (
<EmptyChart />
) : (
@@ -848,7 +848,7 @@ function ActivityTrendCard({ data }: { data: BlogAnalyticsSchema["activity_trend
saves: { label: "ذخیره", color: PALETTE.cyan },
comments: { label: "کامنت", color: PALETTE.amber },
}}
className="h-[320px] w-full"
className="h-[280px] w-full sm:h-[320px]"
>
<LineChart data={data} margin={{ top: 16, right: 64, bottom: 32, left: 18 }}>
<CartesianGrid vertical={false} strokeDasharray="3 3" />
@@ -881,16 +881,16 @@ function BlogEngagementCard({ group }: { group: BlogAnalyticsSchema["post_popula
const labelAxisWidth = axisWidth(data.map((post) => ({ label: post.title, value: post.score })));
return (
<Card>
<CardHeader>
<CardTitle>محبوبیت نوشتهها</CardTitle>
<CardHeader className="p-4 sm:p-6">
<CardTitle className="text-base sm:text-lg">محبوبیت نوشتهها</CardTitle>
<CardDescription>رتبهبندی بر اساس مجموع لایک، ذخیره و کامنت</CardDescription>
</CardHeader>
<CardContent>
<CardContent className="p-4 pt-0 sm:p-6 sm:pt-0">
{!data.length ? (
<EmptyChart />
) : (
<>
<ChartViewport minWidth={620}>
<ChartViewport minWidth={460}>
<ChartContainer
config={{
likes: { label: "لایک", color: PALETTE.rose },
@@ -945,11 +945,11 @@ function BlogEngagementCard({ group }: { group: BlogAnalyticsSchema["post_popula
function TopEventsCard({ group }: { group: EventAnalyticsSchema["top_events"] }) {
return (
<Card>
<CardHeader>
<CardTitle>رویدادهای برتر</CardTitle>
<CardHeader className="p-4 sm:p-6">
<CardTitle className="text-base sm:text-lg">رویدادهای برتر</CardTitle>
<CardDescription>بر اساس شرکتکننده تاییدشده، درآمد و زمان برگزاری</CardDescription>
</CardHeader>
<CardContent className="space-y-3">
<CardContent className="space-y-3 p-4 pt-0 sm:p-6 sm:pt-0">
{group.top_items.length ? (
group.top_items.map((event, index) => (
<div key={event.id} className="flex items-center justify-between gap-3 rounded-xl border bg-background/70 p-3">
@@ -980,11 +980,11 @@ function TopEventsCard({ group }: { group: EventAnalyticsSchema["top_events"] })
function TopPostsCard({ posts }: { posts: BlogAnalyticsSchema["top_posts"] }) {
return (
<Card>
<CardHeader>
<CardTitle>نوشتههای برتر</CardTitle>
<CardHeader className="p-4 sm:p-6">
<CardTitle className="text-base sm:text-lg">نوشتههای برتر</CardTitle>
<CardDescription>بر اساس جمع لایک، ذخیره و کامنت</CardDescription>
</CardHeader>
<CardContent className="space-y-3">
<CardContent className="space-y-3 p-4 pt-0 sm:p-6 sm:pt-0">
{posts.length ? (
posts.map((post, index) => (
<div key={post.id} className="flex items-center justify-between gap-3 rounded-xl border bg-background/70 p-3">
@@ -1235,17 +1235,15 @@ export default function AdminDashboard() {
return (
<div className="space-y-6" dir="rtl">
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<div>
<h2 className="text-2xl font-black tracking-tight">داشبورد دستاوردها</h2>
<p className="mt-1 text-sm text-muted-foreground">
گزارشهای جداگانه برای کاربران، رویدادها و بلاگ با فیلترهای مستقل و خوانا
</p>
</div>
</div>
<Tabs dir="rtl" value={activeTab} onValueChange={(value) => setActiveTab(parseTab(value))} className="space-y-6">
<TabsList className="grid h-auto w-full grid-cols-3 rounded-2xl p-1 sm:w-fit">
<div className="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
<div>
<h2 className="text-xl font-black tracking-tight sm:text-2xl">داشبورد دستاوردها</h2>
<p className="mt-1 text-xs leading-6 text-muted-foreground sm:text-sm">
گزارشهای جداگانه برای کاربران، رویدادها و بلاگ با فیلترهای مستقل و خوانا
</p>
</div>
<TabsList className="grid h-auto w-full grid-cols-3 rounded-2xl p-1 lg:w-fit">
<TabsTrigger value="users" className="gap-2 rounded-xl py-2">
<UsersRound className="h-4 w-4" />
کاربران
@@ -1258,7 +1256,8 @@ export default function AdminDashboard() {
<Tags className="h-4 w-4" />
بلاگ
</TabsTrigger>
</TabsList>
</TabsList>
</div>
<TabsContent value="users">
<UsersSection filters={usersFilters} onFiltersChange={setUsersFilters} />
</TabsContent>