diff --git a/app/requestHandler/use-admin-hooks.ts b/app/requestHandler/use-admin-hooks.ts index c87ae66..eb63837 100644 --- a/app/requestHandler/use-admin-hooks.ts +++ b/app/requestHandler/use-admin-hooks.ts @@ -365,6 +365,62 @@ export interface AdminCollectionRow { createdAt: string; } +/* ---------------------- AI pipelines health (3c) ---------------------- */ + +interface FailureRow { + id: string; + error: string; + createdAt?: string; + updatedAt?: string; + productId?: string; + sellerId?: string; +} + +export interface AiHealthOverview { + generatedAt: string; + tryon: { + total: number; + byStatus: Record; + last24h: number; + last7d: number; + recentFailures: FailureRow[]; + }; + metadata: { + total: number; + byStatus: Record; + last24h: number; + last7d: number; + recentFailures: FailureRow[]; + }; + instagram: { + accountsTotal: number; + accountsActive: number; + postsTotal: number; + postsPendingAi: number; + postsConverted: number; + importedLast7d: number; + recentImports: { account: string; date: string; importedCount: number }[]; + }; + apgs: { + total: number; + success: number; + failed: number; + last24h: number; + last7d: number; + recentFailures: FailureRow[]; + }; +} + +export function useAiHealth() { + const token = useAuthToken(); + return useQuery({ + queryKey: ["admin", "ai-health"], + enabled: !!token, + refetchInterval: 60_000, + queryFn: () => adminGet("ai-health/overview/", token as string), + }); +} + export const useAdminDiscounts = (params: AdminListParams) => useAdminList("discounts", params); export const useAdminCollections = (params: AdminListParams) => diff --git a/app/routes/admin.ai-health.tsx b/app/routes/admin.ai-health.tsx new file mode 100644 index 0000000..d497ae7 --- /dev/null +++ b/app/routes/admin.ai-health.tsx @@ -0,0 +1,182 @@ +import { Loader2, Sparkles, FileText, Instagram, Bot } from "lucide-react"; +import type { LucideIcon } from "lucide-react"; +import { useAiHealth, type AiHealthOverview } from "~/requestHandler/use-admin-hooks"; +import { KpiCard } from "~/components/admin/KpiCard"; +import { AdminPageTitle, faNum, faDate } from "~/components/admin/DataTable"; + +const STATUS_LABEL: Record = { + pending: "در انتظار", + processing: "در حال پردازش", + success: "موفق", + ready: "آماده", + failed: "ناموفق", +}; + +const STATUS_TONE: Record = { + pending: "bg-yellow-500/10 text-yellow-600", + processing: "bg-VITROWN_BLUE/10 text-VITROWN_BLUE", + success: "bg-GREEN/10 text-GREEN", + ready: "bg-GREEN/10 text-GREEN", + failed: "bg-RED/10 text-RED", +}; + +function StatusPills({ byStatus }: { byStatus: Record }) { + const entries = Object.entries(byStatus); + if (entries.length === 0) return

داده‌ای نیست

; + return ( +
+ {entries.map(([k, v]) => ( + + {STATUS_LABEL[k] || k}: {faNum(v)} + + ))} +
+ ); +} + +function Failures({ + rows, + dateKey, +}: { + rows: AiHealthOverview["tryon"]["recentFailures"]; + dateKey: "createdAt" | "updatedAt"; +}) { + if (rows.length === 0) + return

خطای اخیری ثبت نشده ✓

; + return ( +
+

خطاهای اخیر

+ {rows.map((r) => ( +
+
+ + {r.id.slice(0, 8)} + + + {faDate(r[dateKey])} + +
+

+ {r.error || "—"} +

+
+ ))} +
+ ); +} + +function Section({ + title, + icon: Icon, + children, +}: { + title: string; + icon: LucideIcon; + children: React.ReactNode; +}) { + return ( +
+
+
+ +
+

{title}

+
+ {children} +
+ ); +} + +export default function AdminAiHealth() { + const { data, isLoading, isError } = useAiHealth(); + + if (isLoading) + return ( +
+ +
+ ); + if (isError || !data) + return

خطا در بارگذاری وضعیت سرویس‌های هوش مصنوعی

; + + return ( +
+ سلامت سرویس‌های هوش مصنوعی + +
+
+
+ + + +
+
+ +
+ +
+ +
+
+ + + +
+
+ +
+ +
+ +
+
+ + 0 ? "red" : "default"} + /> + +
+

+ ایمپورت ۷ روز اخیر: {faNum(data.instagram.importedLast7d)} +

+ {data.instagram.recentImports.length > 0 && ( +
+ {data.instagram.recentImports.map((im, i) => ( +
+ @{im.account} + + {faNum(im.importedCount)} — {faDate(im.date)} + +
+ ))} +
+ )} +
+ +
+
+ + + 0 ? "red" : "default"} + /> +
+ +
+
+
+ ); +} diff --git a/app/routes/admin.tsx b/app/routes/admin.tsx index 4969bd2..88be149 100644 --- a/app/routes/admin.tsx +++ b/app/routes/admin.tsx @@ -8,6 +8,7 @@ import { Ticket, LayoutGrid, Landmark, + Activity, ScrollText, ArrowRight, ShieldCheck, @@ -33,6 +34,7 @@ const NAV: NavItem[] = [ { label: "تخفیف‌ها", to: "/admin/discounts", icon: Ticket }, { label: "مجموعه‌ها", to: "/admin/collections", icon: LayoutGrid }, { label: "دفتر مالی", to: "/admin/ledger", icon: Landmark }, + { label: "سلامت AI", to: "/admin/ai-health", icon: Activity }, { label: "گزارش فعالیت", to: "/admin/audit", icon: ScrollText }, ];