الملفات
boutmoun123 8863f61d00
فشلت بعض الفحوصات
Deploy To Ghaymah / deploy (push) Has been cancelled
Add Oudelaa dashboard API integration
2026-05-25 20:36:52 +03:00

247 أسطر
8.2 KiB
TypeScript

"use client";
import { useEffect, useMemo, useState } from "react";
import { NoPermissionState } from "@/components/auth/no-permission-state";
import { useSuperAdminSession } from "@/components/auth/session-context";
import { ChannelPieChart, InsightBarChart } from "@/components/dashboard/charts";
import { PageHeader } from "@/components/dashboard/page-header";
import { StatCard } from "@/components/dashboard/stat-card";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { EmptyState } from "@/components/ui/empty-state";
import { useToast } from "@/components/ui/toast";
import { getSuperAdminCharts, getSuperAdminOverview } from "@/lib/api/superadmin";
import { SUPERADMIN_PERMISSIONS, hasPermission } from "@/lib/permissions";
import type {
SuperAdminBreakdownItem,
SuperAdminChartsResponse,
SuperAdminChartPoint,
SuperAdminOverviewResponse,
} from "@/types/api";
import type { ChannelPoint, Insight, StatMetric } from "@/types";
type AnalyticsSnapshot = {
overview: SuperAdminOverviewResponse | null;
charts: SuperAdminChartsResponse | null;
};
function toInsight(points: SuperAdminChartPoint[]): Insight[] {
return points.map((point) => ({ label: point.label, value: point.count }));
}
function toInsightBreakdown(items: SuperAdminBreakdownItem[]): Insight[] {
return items.map((item) => ({ label: item.label, value: item.value }));
}
function toChannel(items: SuperAdminBreakdownItem[]): ChannelPoint[] {
return items.map((item) => ({ name: item.label, value: item.value }));
}
export default function AnalyticsPage() {
const { permissions } = useSuperAdminSession();
const [snapshot, setSnapshot] = useState<AnalyticsSnapshot>({
overview: null,
charts: null,
});
const [loading, setLoading] = useState(true);
const { toast } = useToast();
const canReadAnalytics = hasPermission(permissions, SUPERADMIN_PERMISSIONS.ANALYTICS_READ);
const canReadOverview = hasPermission(permissions, SUPERADMIN_PERMISSIONS.OVERVIEW_READ);
useEffect(() => {
let active = true;
const loadAnalytics = async () => {
if (!canReadAnalytics) {
setLoading(false);
return;
}
setLoading(true);
try {
const [overview, charts] = await Promise.all([
canReadOverview ? getSuperAdminOverview() : Promise.resolve(null),
getSuperAdminCharts({ range: "30d" }),
]);
if (!active) return;
setSnapshot({ overview, charts });
} catch (error) {
if (!active) return;
toast({ title: "Failed to load analytics", description: String(error), variant: "danger" });
} finally {
if (active) setLoading(false);
}
};
void loadAnalytics();
return () => {
active = false;
};
}, [canReadAnalytics, canReadOverview, toast]);
const metrics = snapshot.overview?.metrics;
const dashboardMetrics: StatMetric[] = useMemo(
() => [
{
id: "users",
label: "Total users",
value: loading ? "..." : String(metrics?.usersCount ?? 0),
delta: `${metrics?.adminsCount ?? 0} admin accounts`,
trend: "up",
},
{
id: "posts",
label: "Published content",
value: loading ? "..." : String(metrics?.postsCount ?? 0),
delta: `${metrics?.commentsCount ?? 0} tracked comments`,
trend: "up",
},
{
id: "marketplace",
label: "Marketplace",
value: loading ? "..." : String(metrics?.marketplaceListingsCount ?? 0),
delta: `${metrics?.repairShopsCount ?? 0} repair shops`,
trend: "neutral",
},
{
id: "moderation",
label: "Moderation load",
value: loading
? "..."
: String((metrics?.flaggedPostsCount ?? 0) + (metrics?.flaggedCommentsCount ?? 0)),
delta: `${metrics?.hiddenPostsCount ?? 0} hidden posts and ${metrics?.hiddenCommentsCount ?? 0} hidden comments`,
trend:
(metrics?.flaggedPostsCount ?? 0) + (metrics?.flaggedCommentsCount ?? 0) > 0
? "down"
: "neutral",
},
],
[loading, metrics],
);
const charts = snapshot.charts;
const userSeries = toInsight(charts?.series.users ?? []);
const postSeries = toInsight(charts?.series.posts ?? []);
const listingSeries = toInsight(charts?.series.listings ?? []);
const roleBreakdown = toChannel(charts?.breakdowns.userRoles ?? []);
const listingBreakdown = toInsightBreakdown(charts?.breakdowns.listingCategories ?? []);
const moderationBreakdown = toInsightBreakdown(charts?.breakdowns.moderation ?? []);
if (!canReadAnalytics) {
return (
<div className="space-y-5 pb-8">
<PageHeader
title="Analytics"
subtitle="Charts and operational trends for users, content, moderation, and marketplace activity."
/>
<NoPermissionState description="This page needs the analytics.read permission." />
</div>
);
}
return (
<div className="space-y-5 pb-8">
<PageHeader
title="Analytics"
subtitle="Operational charts for growth, moderation, and marketplace activity."
/>
{canReadOverview ? (
<section className="grid gap-4 md:grid-cols-2 xl:grid-cols-4">
{dashboardMetrics.map((metric) => (
<StatCard key={metric.id} metric={metric} />
))}
</section>
) : null}
<section className="grid gap-4 xl:grid-cols-12">
<Card className="xl:col-span-6">
<CardHeader>
<CardTitle>User signups - last 30 days</CardTitle>
</CardHeader>
<CardContent>
{userSeries.length ? (
<InsightBarChart data={userSeries} />
) : (
<EmptyState title="No data" description="Not enough user activity to draw this chart." />
)}
</CardContent>
</Card>
<Card className="xl:col-span-6">
<CardHeader>
<CardTitle>Published content - last 30 days</CardTitle>
</CardHeader>
<CardContent>
{postSeries.length ? (
<InsightBarChart data={postSeries} />
) : (
<EmptyState title="No data" description="Not enough content activity to draw this chart." />
)}
</CardContent>
</Card>
</section>
<section className="grid gap-4 xl:grid-cols-12">
<Card className="xl:col-span-5">
<CardHeader>
<CardTitle>User role distribution</CardTitle>
</CardHeader>
<CardContent>
{roleBreakdown.length ? (
<ChannelPieChart data={roleBreakdown} />
) : (
<EmptyState title="No role data" description="No role breakdown is currently available." />
)}
</CardContent>
</Card>
<Card className="xl:col-span-7">
<CardHeader>
<CardTitle>Marketplace categories</CardTitle>
</CardHeader>
<CardContent>
{listingBreakdown.length ? (
<InsightBarChart data={listingBreakdown} />
) : (
<EmptyState title="No data" description="No category breakdown is available right now." />
)}
</CardContent>
</Card>
</section>
<section className="grid gap-4 xl:grid-cols-12">
<Card className="xl:col-span-6">
<CardHeader>
<CardTitle>Marketplace creation - last 30 days</CardTitle>
</CardHeader>
<CardContent>
{listingSeries.length ? (
<InsightBarChart data={listingSeries} />
) : (
<EmptyState title="No data" description="Not enough marketplace activity to draw this chart." />
)}
</CardContent>
</Card>
<Card className="xl:col-span-6">
<CardHeader>
<CardTitle>Current moderation states</CardTitle>
</CardHeader>
<CardContent>
{moderationBreakdown.length ? (
<InsightBarChart data={moderationBreakdown} />
) : (
<EmptyState title="No data" description="No moderation breakdown is available right now." />
)}
</CardContent>
</Card>
</section>
</div>
);
}