Harden media health checks and duration extraction
فشلت بعض الفحوصات
Deploy To Ghaymah / deploy (push) Has been cancelled
فشلت بعض الفحوصات
Deploy To Ghaymah / deploy (push) Has been cancelled
هذا الالتزام موجود في:
@@ -2,7 +2,16 @@
|
||||
|
||||
import Link from "next/link";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { Boxes, RefreshCcw, ShieldAlert, Store, Users2 } from "lucide-react";
|
||||
import {
|
||||
Boxes,
|
||||
FileText,
|
||||
MessageSquareText,
|
||||
RefreshCcw,
|
||||
ShieldAlert,
|
||||
Store,
|
||||
UserRound,
|
||||
Users2,
|
||||
} from "lucide-react";
|
||||
|
||||
import { NoPermissionState } from "@/components/auth/no-permission-state";
|
||||
import { PostPreviewCard } from "@/components/dashboard/post-preview-card";
|
||||
@@ -69,6 +78,34 @@ function ShortcutLink({
|
||||
);
|
||||
}
|
||||
|
||||
function getActivityIcon(type: string) {
|
||||
const normalized = type.toLowerCase();
|
||||
if (normalized.includes("user")) return UserRound;
|
||||
if (normalized.includes("comment")) return MessageSquareText;
|
||||
if (normalized.includes("listing") || normalized.includes("shop")) return Store;
|
||||
if (normalized.includes("case") || normalized.includes("audit") || normalized.includes("report")) {
|
||||
return ShieldAlert;
|
||||
}
|
||||
return FileText;
|
||||
}
|
||||
|
||||
function ActivityTypeIcon({ type }: { type: string }) {
|
||||
const Icon = getActivityIcon(type);
|
||||
return <Icon className="h-4 w-4" />;
|
||||
}
|
||||
|
||||
function getActivityLabel(type: string) {
|
||||
const normalized = type.toLowerCase();
|
||||
if (normalized === "post") return "New post";
|
||||
if (normalized === "user") return "New user";
|
||||
if (normalized === "comment") return "New comment";
|
||||
if (normalized === "listing") return "Marketplace listing";
|
||||
if (normalized === "repair_shop") return "Repair shop";
|
||||
if (normalized === "case") return "Moderation case";
|
||||
if (normalized === "audit") return "Audit event";
|
||||
return type.replace(/_/g, " ");
|
||||
}
|
||||
|
||||
export default function DashboardPage() {
|
||||
const { permissions } = useSuperAdminSession();
|
||||
const [snapshot, setSnapshot] = useState<DashboardSnapshot>({
|
||||
@@ -417,26 +454,33 @@ export default function DashboardPage() {
|
||||
}
|
||||
>
|
||||
<CardHeader className="flex flex-row items-center justify-between">
|
||||
<CardTitle>Recent activity</CardTitle>
|
||||
<CardTitle>Platform activity</CardTitle>
|
||||
<Link href="/analytics" className="text-sm text-primary">
|
||||
Activity feeds
|
||||
View analytics
|
||||
</Link>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{!snapshot.recentActivity.length ? (
|
||||
<EmptyState
|
||||
title="No recent activity"
|
||||
title="No platform activity"
|
||||
description="Operational and moderation activity will appear here."
|
||||
/>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
{snapshot.recentActivity.map((item, index) => (
|
||||
{snapshot.recentActivity.slice(0, 5).map((item, index) => (
|
||||
<div
|
||||
key={`${item.type}-${index}`}
|
||||
className="rounded-xl border border-border/70 bg-secondary/20 p-3"
|
||||
className="rounded-lg border border-border/70 bg-secondary/20 p-3 transition hover:border-primary/40 hover:bg-secondary/30"
|
||||
>
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div className="text-sm font-medium text-foreground">{item.title}</div>
|
||||
<div className="flex min-w-0 items-center gap-3">
|
||||
<span className="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg border border-border/70 bg-background/50 text-primary">
|
||||
<ActivityTypeIcon type={item.type} />
|
||||
</span>
|
||||
<div dir="auto" className="truncate text-sm font-semibold text-foreground">
|
||||
{item.title || "Untitled activity"}
|
||||
</div>
|
||||
</div>
|
||||
<Badge
|
||||
variant={
|
||||
item.status === "flagged" || item.status === "disabled"
|
||||
@@ -444,11 +488,11 @@ export default function DashboardPage() {
|
||||
: "muted"
|
||||
}
|
||||
>
|
||||
{item.type}
|
||||
{getActivityLabel(item.type)}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="mt-1 text-xs text-muted-foreground">
|
||||
{item.subtitle} • {formatDateTime(item.createdAt)}
|
||||
<div dir="auto" className="mt-2 truncate text-xs text-muted-foreground">
|
||||
{item.subtitle || "No additional details"} - {formatDateTime(item.createdAt)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
export function formatDateTime(value?: string | null) {
|
||||
if (!value) return "-";
|
||||
try {
|
||||
return new Intl.DateTimeFormat("ar-SA", {
|
||||
dateStyle: "medium",
|
||||
timeStyle: "short",
|
||||
return new Intl.DateTimeFormat("en-US", {
|
||||
year: "numeric",
|
||||
month: "short",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
hour12: true,
|
||||
}).format(new Date(value));
|
||||
} catch {
|
||||
return value;
|
||||
|
||||
المرجع في مشكلة جديدة
حظر مستخدم