diff --git a/dashboard/src/App.jsx b/dashboard/src/App.jsx index a5ef211..18a597e 100644 --- a/dashboard/src/App.jsx +++ b/dashboard/src/App.jsx @@ -28,6 +28,7 @@ import RealtimeAnalytics from "@/components/dashboard/RealtimeAnalytics"; import UserBehaviorDashboard from "@/components/dashboard/UserBehaviorDashboard"; import TypeformDashboard from "@/components/dashboard/TypeformDashboard"; import MiniStatCards from "@/components/dashboard/MiniStatCards"; +import MiniRealtimeAnalytics from "@/components/dashboard/MiniRealtimeAnalytics"; // Public layout const PublicLayout = () => ( @@ -64,6 +65,7 @@ const PinProtectedLayout = ({ children }) => { const SmallLayout = () => { const DATETIME_SCALE = 2; const STATS_SCALE = 1.65; + const ANALYTICS_SCALE = 1.5; return (
@@ -99,7 +101,17 @@ const SmallLayout = () => {
- {/* You can easily add more grid items here */} +
+
+ +
+
); diff --git a/dashboard/src/components/dashboard/MiniRealtimeAnalytics.jsx b/dashboard/src/components/dashboard/MiniRealtimeAnalytics.jsx new file mode 100644 index 0000000..1398ee7 --- /dev/null +++ b/dashboard/src/components/dashboard/MiniRealtimeAnalytics.jsx @@ -0,0 +1,200 @@ +import React, { useState, useEffect } from "react"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { + BarChart, + Bar, + XAxis, + YAxis, + Tooltip, + ResponsiveContainer, +} from "recharts"; +import { AlertTriangle } from "lucide-react"; +import { Alert, AlertDescription } from "@/components/ui/alert"; +import { format } from "date-fns"; +import { + summaryCard, + SkeletonSummaryCard, + SkeletonBarChart, + processBasicData, +} from "./RealtimeAnalytics"; + +const MiniRealtimeAnalytics = () => { + const [basicData, setBasicData] = useState({ + last30MinUsers: 0, + last5MinUsers: 0, + byMinute: [], + tokenQuota: null, + lastUpdated: null, + }); + + const [loading, setLoading] = useState(true); + const [isPaused, setIsPaused] = useState(false); + const [error, setError] = useState(null); + + useEffect(() => { + let basicInterval; + + const fetchBasicData = async () => { + if (isPaused) return; + try { + const response = await fetch("/api/analytics/realtime/basic", { + credentials: "include", + }); + + if (!response.ok) { + throw new Error("Failed to fetch basic realtime data"); + } + + const result = await response.json(); + const processed = processBasicData(result.data); + setBasicData(processed); + setError(null); + setLoading(false); + } catch (error) { + console.error("Error details:", { + message: error.message, + stack: error.stack, + response: error.response, + }); + if (error.message === "QUOTA_EXCEEDED") { + setError("Quota exceeded. Analytics paused until manually resumed."); + setIsPaused(true); + } else { + setError("Failed to fetch analytics data"); + } + } + }; + + // Initial fetch + fetchBasicData(); + + // Set up interval + basicInterval = setInterval(fetchBasicData, 30000); // 30 seconds + + return () => { + clearInterval(basicInterval); + }; + }, [isPaused]); + + if (loading && !basicData) { + return ( + + +
+ + Real-Time Analytics + + +
+
+ + +
+ + +
+ +
+
+ ); + } + + return ( + + +
+ + Real-Time Analytics + +
+ {format(new Date(basicData.lastUpdated), "h:mm a")} +
+
+
+ + + {error && ( + + + {error} + + )} + +
+ {summaryCard( + "Last 30 minutes", + "Active users", + basicData.last30MinUsers, + { + colorClass: "text-purple-200", + titleClass: "text-purple-100 font-bold text-md", + descriptionClass: "text-purple-200 text-md font-semibold", + background: "bg-gradient-to-br from-purple-800 to-purple-700" + } + )} + {summaryCard( + "Last 5 minutes", + "Active users", + basicData.last5MinUsers, + { + colorClass: "text-purple-200", + titleClass: "text-purple-100 font-bold text-md", + descriptionClass: "text-purple-200 text-md font-semibold", + background: "bg-gradient-to-br from-purple-800 to-purple-700" + } + )} +
+ +
+ + + value + "m"} + className="text-xs" + tick={{ fill: "#E9D5FF" }} + /> + + { + if (active && payload && payload.length) { + const timestamp = new Date( + Date.now() + payload[0].payload.minute * 60000 + ); + return ( + + +

+ {format(timestamp, "h:mm a")} +

+
+ + Active Users: + + + {payload[0].value.toLocaleString()} + +
+
+
+ ); + } + return null; + }} + /> + +
+
+
+
+
+ ); +}; + +export default MiniRealtimeAnalytics; \ No newline at end of file diff --git a/dashboard/src/components/dashboard/RealtimeAnalytics.jsx b/dashboard/src/components/dashboard/RealtimeAnalytics.jsx index ab37aad..e8a1dd2 100644 --- a/dashboard/src/components/dashboard/RealtimeAnalytics.jsx +++ b/dashboard/src/components/dashboard/RealtimeAnalytics.jsx @@ -636,4 +636,14 @@ export const RealtimeAnalytics = () => { ); }; +export { + summaryCard, + SkeletonSummaryCard, + SkeletonBarChart, + SkeletonTable, + processBasicData, + METRIC_COLORS, + QuotaInfo +}; + export default RealtimeAnalytics;