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;