From 925eda8677b3500f5d619f22ff50438eaec1e974 Mon Sep 17 00:00:00 2001 From: Matt Date: Wed, 1 Jan 2025 22:23:50 -0500 Subject: [PATCH] "Fix" grid positioning, style minirealtimeanalytics --- dashboard/src/App.jsx | 61 ++-- .../dashboard/MiniRealtimeAnalytics.jsx | 47 ++- .../dashboard/RealtimeAnalytics.jsx | 303 ++++++++---------- 3 files changed, 182 insertions(+), 229 deletions(-) diff --git a/dashboard/src/App.jsx b/dashboard/src/App.jsx index 18a597e..e2404ca 100644 --- a/dashboard/src/App.jsx +++ b/dashboard/src/App.jsx @@ -65,8 +65,8 @@ const PinProtectedLayout = ({ children }) => { const SmallLayout = () => { const DATETIME_SCALE = 2; const STATS_SCALE = 1.65; - const ANALYTICS_SCALE = 1.5; - + const ANALYTICS_SCALE = 1.65; + return (
@@ -74,42 +74,39 @@ const SmallLayout = () => {
-
-
- + {/* DateTime */} +
+
+
-
-
+
+
- + transformOrigin: 'top left', + width: `${100/STATS_SCALE}%` + }}> + +
-
- -
-
+
- + transformOrigin: 'top left', + width: `${100/ANALYTICS_SCALE}%` + }}> + +
diff --git a/dashboard/src/components/dashboard/MiniRealtimeAnalytics.jsx b/dashboard/src/components/dashboard/MiniRealtimeAnalytics.jsx index 1398ee7..06687b3 100644 --- a/dashboard/src/components/dashboard/MiniRealtimeAnalytics.jsx +++ b/dashboard/src/components/dashboard/MiniRealtimeAnalytics.jsx @@ -100,19 +100,9 @@ const MiniRealtimeAnalytics = () => { } return ( - - -
- - Real-Time Analytics - -
- {format(new Date(basicData.lastUpdated), "h:mm a")} -
-
-
+ - + {error && ( @@ -126,10 +116,10 @@ const MiniRealtimeAnalytics = () => { "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" + colorClass: "text-sky-200", + titleClass: "text-sky-100 font-bold text-md", + descriptionClass: "text-sky-200 text-md font-semibold", + background: "bg-gradient-to-br from-sky-800 to-sky-700" } )} {summaryCard( @@ -137,15 +127,15 @@ const MiniRealtimeAnalytics = () => { "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" + colorClass: "text-sky-200", + titleClass: "text-sky-100 font-bold text-md", + descriptionClass: "text-sky-200 text-md font-semibold", + background: "bg-gradient-to-br from-sky-800 to-sky-700" } )}
-
+
{ dataKey="minute" tickFormatter={(value) => value + "m"} className="text-xs" - tick={{ fill: "#E9D5FF" }} + tick={{ fill: "#BAE6FD" }} /> { @@ -168,16 +158,16 @@ const MiniRealtimeAnalytics = () => { Date.now() + payload[0].payload.minute * 60000 ); return ( - + -

+

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

- + Active Users: - + {payload[0].value.toLocaleString()}
@@ -188,12 +178,11 @@ const MiniRealtimeAnalytics = () => { return null; }} /> - +
- ); }; diff --git a/dashboard/src/components/dashboard/RealtimeAnalytics.jsx b/dashboard/src/components/dashboard/RealtimeAnalytics.jsx index e8a1dd2..334c28d 100644 --- a/dashboard/src/components/dashboard/RealtimeAnalytics.jsx +++ b/dashboard/src/components/dashboard/RealtimeAnalytics.jsx @@ -32,7 +32,7 @@ import { import { format } from "date-fns"; import { Skeleton } from "@/components/ui/skeleton"; -const METRIC_COLORS = { +export const METRIC_COLORS = { activeUsers: { color: "#8b5cf6", className: "text-purple-600 dark:text-purple-400", @@ -47,50 +47,154 @@ const METRIC_COLORS = { }, }; -const formatNumber = (value, decimalPlaces = 0) => { - return new Intl.NumberFormat("en-US", { - minimumFractionDigits: decimalPlaces, - maximumFractionDigits: decimalPlaces, - }).format(value || 0); -}; - -const formatPercent = (value, decimalPlaces = 1) => - `${(value || 0).toFixed(decimalPlaces)}%`; - -const summaryCard = (label, sublabel, value, options = {}) => { +export const summaryCard = (label, sublabel, value, options = {}) => { const { - isMonetary = false, - isPercentage = false, - decimalPlaces = 0, - colorClass = METRIC_COLORS.activeUsers.className, + colorClass = "text-gray-900 dark:text-gray-100", + titleClass = "text-sm font-medium text-gray-600 dark:text-gray-300", + descriptionClass = "text-sm text-muted-foreground", + background = "bg-white dark:bg-gray-900/60", } = options; - let displayValue; - if (isMonetary) { - displayValue = formatCurrency(value, decimalPlaces); - } else if (isPercentage) { - displayValue = formatPercent(value, decimalPlaces); - } else { - displayValue = formatNumber(value, decimalPlaces); - } - return ( - + - {label} + {label}
- {displayValue} + {value.toLocaleString()}
-
{sublabel}
+
{sublabel}
); }; -const QuotaInfo = ({ tokenQuota }) => { - // Add early return if tokenQuota is null or undefined +export const SkeletonSummaryCard = () => ( + + + + + + + + + +); + +export const SkeletonBarChart = () => ( +
+
+ {/* Grid lines */} + {[...Array(5)].map((_, i) => ( +
+ ))} + {/* Y-axis labels */} +
+ {[...Array(5)].map((_, i) => ( + + ))} +
+ {/* X-axis labels */} +
+ {[...Array(6)].map((_, i) => ( + + ))} +
+ {/* Bars */} +
+ {[...Array(30)].map((_, i) => ( +
+ ))} +
+
+
+); + +export const SkeletonTable = () => ( +
+ + + + + + + + + + + + + {[...Array(8)].map((_, i) => ( + + + + + + + + + ))} + +
+
+); + +export const processBasicData = (data) => { + const last30MinUsers = parseInt( + data.userResponse?.rows?.[0]?.metricValues?.[0]?.value || 0 + ); + const last5MinUsers = parseInt( + data.fiveMinResponse?.rows?.[0]?.metricValues?.[0]?.value || 0 + ); + + const byMinute = Array.from({ length: 30 }, (_, i) => { + const matchingRow = data.timeSeriesResponse?.rows?.find( + (row) => parseInt(row.dimensionValues[0].value) === i + ); + const users = matchingRow + ? parseInt(matchingRow.metricValues[0].value) + : 0; + const timestamp = new Date(Date.now() - i * 60000); + return { + minute: -i, + users, + timestamp: timestamp.toLocaleTimeString([], { + hour: "2-digit", + minute: "2-digit", + }), + }; + }).reverse(); + + const tokenQuota = data.quotaInfo + ? { + projectHourly: data.quotaInfo.projectHourly || {}, + daily: data.quotaInfo.daily || {}, + serverErrors: data.quotaInfo.serverErrors || {}, + thresholdedRequests: data.quotaInfo.thresholdedRequests || {}, + } + : null; + + return { + last30MinUsers, + last5MinUsers, + byMinute, + tokenQuota, + lastUpdated: new Date().toISOString(), + }; +}; + +export const QuotaInfo = ({ tokenQuota }) => { if (!tokenQuota || typeof tokenQuota !== "object") return null; const { @@ -100,7 +204,6 @@ const QuotaInfo = ({ tokenQuota }) => { thresholdedRequests = {}, } = tokenQuota; - // Add null checks and default values for all properties const { remaining: projectHourlyRemaining = 0, consumed: projectHourlyConsumed = 0, @@ -116,13 +219,11 @@ const QuotaInfo = ({ tokenQuota }) => { consumed: thresholdConsumed = 0, } = thresholdedRequests; - // Calculate percentages with safe math const hourlyPercentage = ((projectHourlyRemaining / 14000) * 100).toFixed(1); const dailyPercentage = ((dailyRemaining / 200000) * 100).toFixed(1); const errorPercentage = ((errorsRemaining / 10) * 100).toFixed(1); const thresholdPercentage = ((thresholdRemaining / 120) * 100).toFixed(1); - // Determine color based on remaining percentage const getStatusColor = (percentage) => { const numericPercentage = parseFloat(percentage); if (isNaN(numericPercentage) || numericPercentage < 20) @@ -180,86 +281,6 @@ const QuotaInfo = ({ tokenQuota }) => { ); }; -const SkeletonSummaryCard = () => ( - - - - - - - - - -); - -const SkeletonBarChart = () => ( -
-
- {/* Grid lines */} - {[...Array(5)].map((_, i) => ( -
- ))} - {/* Y-axis labels */} -
- {[...Array(5)].map((_, i) => ( - - ))} -
- {/* X-axis labels */} -
- {[...Array(6)].map((_, i) => ( - - ))} -
- {/* Bars */} -
- {[...Array(30)].map((_, i) => ( -
- ))} -
-
-
-); - -const SkeletonTable = () => ( -
- - - - - - - - - - - - - {[...Array(8)].map((_, i) => ( - - - - - - - - - ))} - -
-
-); - export const RealtimeAnalytics = () => { const [basicData, setBasicData] = useState({ last30MinUsers: 0, @@ -279,50 +300,6 @@ export const RealtimeAnalytics = () => { const [isPaused, setIsPaused] = useState(false); const [error, setError] = useState(null); - const processBasicData = (data) => { - const last30MinUsers = parseInt( - data.userResponse?.rows?.[0]?.metricValues?.[0]?.value || 0 - ); - const last5MinUsers = parseInt( - data.fiveMinResponse?.rows?.[0]?.metricValues?.[0]?.value || 0 - ); - - const byMinute = Array.from({ length: 30 }, (_, i) => { - const matchingRow = data.timeSeriesResponse?.rows?.find( - (row) => parseInt(row.dimensionValues[0].value) === i - ); - const users = matchingRow - ? parseInt(matchingRow.metricValues[0].value) - : 0; - const timestamp = new Date(Date.now() - i * 60000); - return { - minute: -i, - users, - timestamp: timestamp.toLocaleTimeString([], { - hour: "2-digit", - minute: "2-digit", - }), - }; - }).reverse(); - - const tokenQuota = data.quotaInfo - ? { - projectHourly: data.quotaInfo.projectHourly || {}, - daily: data.quotaInfo.daily || {}, - serverErrors: data.quotaInfo.serverErrors || {}, - thresholdedRequests: data.quotaInfo.thresholdedRequests || {}, - } - : null; - - return { - last30MinUsers, - last5MinUsers, - byMinute, - tokenQuota, - lastUpdated: new Date().toISOString(), - }; - }; - const processDetailedData = (data) => { return { currentPages: @@ -636,14 +613,4 @@ export const RealtimeAnalytics = () => { ); }; -export { - summaryCard, - SkeletonSummaryCard, - SkeletonBarChart, - SkeletonTable, - processBasicData, - METRIC_COLORS, - QuotaInfo -}; - export default RealtimeAnalytics;