diff --git a/src/components/features/admin/tabs/overview-tab.tsx b/src/components/features/admin/tabs/overview-tab.tsx index cf3a440..96ea96c 100644 --- a/src/components/features/admin/tabs/overview-tab.tsx +++ b/src/components/features/admin/tabs/overview-tab.tsx @@ -1,6 +1,6 @@ "use client"; -import React, { useEffect, useState, useCallback } from "react"; +import React, { useEffect, useState, useCallback, useMemo } from "react"; import { RefreshCw, Settings, Radio, BarChart3, X } from "lucide-react"; import { useToast } from "@/components/ui/use-toast"; import { @@ -40,6 +40,71 @@ export function OverviewTab({ const { refresh: refreshStats } = useAdminStats(); const [isRefreshing, setIsRefreshing] = useState(false); + const [systemStatus, setSystemStatus] = useState>({ + website: { state: "checking", label: "Checking..." }, + rsvp: { state: "checking", label: "Checking..." }, + photos: { state: "checking", label: "Checking..." }, + music: { state: "checking", label: "Checking..." }, + guestbook: { state: "checking", label: "Checking..." }, + gate: { state: "checking", label: "Checking..." }, + }); + + const statusConfig = useMemo(() => ([ + { id: "website", label: "Website", endpoint: "/api/health", okLabel: "Online" }, + { id: "rsvp", label: "RSVP System", endpoint: "/api/admin/rsvp", okLabel: "Active" }, + { id: "photos", label: "Photo Upload", endpoint: "/api/admin/gallery", okLabel: "Operational" }, + { id: "music", label: "Music Requests", endpoint: "/api/admin/music", okLabel: "Operational" }, + { id: "guestbook", label: "Guestbook", endpoint: "/api/admin/guestbook", okLabel: "Operational" }, + { id: "gate", label: "Security Gate", endpoint: "/api/admin/security-gate/secret", okLabel: "Online" }, + ] as const), []); + + const checkSystemStatus = useCallback(async (setChecking = false) => { + if (setChecking) { + setSystemStatus((prev) => { + const next = { ...prev }; + statusConfig.forEach((item) => { + next[item.id] = { state: "checking", label: "Checking..." }; + }); + return next; + }); + } + + await Promise.all( + statusConfig.map(async (item) => { + try { + const response = await fetch(item.endpoint, { + method: "GET", + credentials: "include", + cache: "no-store", + }); + + if (response.ok) { + setSystemStatus((prev) => ({ + ...prev, + [item.id]: { state: "ok", label: item.okLabel }, + })); + return; + } + + const label = response.status === 401 + ? "Auth required" + : response.status >= 500 + ? "Service error" + : `Degraded (${response.status})`; + + setSystemStatus((prev) => ({ + ...prev, + [item.id]: { state: response.status >= 500 ? "error" : "degraded", label }, + })); + } catch (error) { + setSystemStatus((prev) => ({ + ...prev, + [item.id]: { state: "error", label: "Offline" }, + })); + } + }) + ); + }, [statusConfig]); const handleRefresh = useCallback(async () => { setIsRefreshing(true); @@ -48,11 +113,12 @@ export function OverviewTab({ fetchRecentActivity(), fetchMusic(), refreshStats(), + checkSystemStatus(true), ]); } finally { setIsRefreshing(false); } - }, [fetchRecentActivity, fetchMusic, refreshStats]); + }, [fetchRecentActivity, fetchMusic, refreshStats, checkSystemStatus]); useEffect(() => { const interval = setInterval(() => { @@ -62,6 +128,15 @@ export function OverviewTab({ return () => clearInterval(interval); }, [fetchRecentActivity]); + useEffect(() => { + checkSystemStatus(true); + const interval = setInterval(() => { + checkSystemStatus(); + }, 30000); + + return () => clearInterval(interval); + }, [checkSystemStatus]); + useAdminRealtime({ onRSVPUpdate: () => handleRefresh(), onGuestbookUpdate: () => fetchRecentActivity(), @@ -216,27 +291,35 @@ export function OverviewTab({ System Status -
- Website -
-
- Online -
-
-
- RSVP System -
-
- Active -
-
-
- Photo Upload -
-
- Working -
-
+ {statusConfig.map((item) => { + const status = systemStatus[item.id] || { state: "checking", label: "Checking..." }; + const dotClass = + status.state === "ok" + ? "bg-green-500" + : status.state === "degraded" + ? "bg-amber-500" + : status.state === "error" + ? "bg-red-500" + : "bg-gray-400"; + const textClass = + status.state === "ok" + ? "text-green-600" + : status.state === "degraded" + ? "text-amber-600" + : status.state === "error" + ? "text-red-600" + : "text-gray-500"; + + return ( +
+ {item.label} +
+
+ {status.label} +
+
+ ); + })}