import { prisma } from "@/lib/prisma" import { auth } from "@/auth" import { redirect } from "next/navigation" import { Activity, Link2, Shuffle, ShoppingCart, Store, Users } from "lucide-react" type ConnectionGroup = { status: string _count: { _all: number } } type ProductSyncGroup = { status: string _count: { _all: number } } type RecentSyncError = { id: string resourceType: string lastSyncAt: Date errorMessage: string | null store: { shopifyStoreName: string } } type RecentEvent = { id: string shopifyProductTitle: string status: string createdAt: Date store: { shopifyStoreName: string } supplier: { companyName: string } } interface AdminOverviewResult { storeCount: number supplierCount: number connectionStats: ConnectionGroup[] productSyncStats: ProductSyncGroup[] orderCount: number recentSyncErrors: RecentSyncError[] recentEvents: RecentEvent[] } async function getAdminOverview(): Promise { const [ storeCount, supplierCount, connectionStats, productSyncStats, orderCount, recentSyncErrors, recentEvents, ] = await Promise.all([ prisma.store.count(), prisma.supplierProfile.count(), prisma.connection.groupBy({ by: ["status"], _count: { _all: true }, }) as unknown as Promise, prisma.productSync.groupBy({ by: ["status"], _count: { _all: true }, } as Parameters[0]) as unknown as Promise, prisma.cachedOrder.count(), prisma.syncMetadata.findMany({ where: { syncStatus: { in: ["failed"] } }, orderBy: { lastSyncAt: "desc" }, take: 5, include: { store: true }, }) as Promise, prisma.productSync.findMany({ orderBy: { createdAt: "desc" }, take: 8, include: { store: true, supplier: true, }, }) as Promise, ]) return { storeCount, supplierCount, connectionStats, productSyncStats, orderCount, recentSyncErrors, recentEvents, } } export default async function AdminOverviewPage() { const session = await auth() if (!session || session.user.role !== "ADMIN") { redirect("/login") } const { storeCount, supplierCount, connectionStats, productSyncStats, orderCount, recentSyncErrors, recentEvents, } = await getAdminOverview() const connectedCount = connectionStats.find((c: ConnectionGroup) => c.status === "CONNECTED")?._count._all ?? 0 const pendingConnections = connectionStats.find((c: ConnectionGroup) => c.status === "PENDING")?._count._all ?? 0 const rejectedConnections = connectionStats.find((c: ConnectionGroup) => c.status === "REJECTED")?._count._all ?? 0 const pendingSyncs = productSyncStats.find((c: ProductSyncGroup) => c.status === "PENDING")?._count._all ?? 0 const acceptedSyncs = productSyncStats.find((c: ProductSyncGroup) => c.status === "ACCEPTED")?._count._all ?? 0 const rejectedSyncs = productSyncStats.find((c: ProductSyncGroup) => c.status === "REJECTED")?._count._all ?? 0 return (

Platform Overview

High-level, read-only visibility into how data flows between Shopify stores, vendors and suppliers across JUNOHUB.

Vendors & Stores

{storeCount}

Total Shopify stores connected

Suppliers

{supplierCount}

Active supplier profiles

Orders Indexed

{orderCount}

Cached Shopify orders across all stores

Connections

Vendor ↔ Supplier relationships

Connected

{connectedCount}

Pending

{pendingConnections}

Rejected

{rejectedConnections}

Product Syncs

Vendor product sharing with suppliers

Pending

{pendingSyncs}

Accepted

{acceptedSyncs}

Rejected

{rejectedSyncs}

Sync Health

Recent sync failures across all stores

{recentSyncErrors.length === 0 && (

No recent sync failures detected.

)} {recentSyncErrors.map((sync: RecentSyncError) => (

{sync.resourceType.toUpperCase()} ·{" "} {sync.store.shopifyStoreName}

{sync.errorMessage || "Unknown error"}

Last attempt:{" "} {sync.lastSyncAt.toLocaleString("en-US", { dateStyle: "short", timeStyle: "short", })}

))}

Recent Events

Latest product sync activity across the network

{recentEvents.length === 0 && (

No recent product sync events.

)} {recentEvents.map((event: RecentEvent) => (

{event.store.shopifyStoreName} {" "} ↔{" "} {event.supplier.companyName}

{event.shopifyProductTitle}

Status:{" "} {event.status} {" "} ·{" "} {event.createdAt.toLocaleString("en-US", { dateStyle: "short", timeStyle: "short", })}

))}
) }