From 89ec46f827fbf849649cf7af2e82479a8db33ac8 Mon Sep 17 00:00:00 2001 From: rocord01 Date: Sat, 26 Jul 2025 23:05:09 -0400 Subject: [PATCH] statistics wowowoowowowow --- src/app/page.jsx | 2 + src/components/HomeTickers.jsx | 207 +++++++++++++++++++++++++++++++++ 2 files changed, 209 insertions(+) create mode 100644 src/components/HomeTickers.jsx diff --git a/src/app/page.jsx b/src/app/page.jsx index de178e3..6249c44 100644 --- a/src/app/page.jsx +++ b/src/app/page.jsx @@ -5,6 +5,7 @@ import Features from '@/components/features' import Team from '@/components/team' import Footer from '@/components/footer' import Updates from '@/components/updates' // Import Blog component +import HomeTickers from '@/components/HomeTickers' export default function Home() { return ( @@ -13,6 +14,7 @@ export default function Home() {
+
diff --git a/src/components/HomeTickers.jsx b/src/components/HomeTickers.jsx new file mode 100644 index 0000000..70c807f --- /dev/null +++ b/src/components/HomeTickers.jsx @@ -0,0 +1,207 @@ +"use client"; + +import { useEffect, useState, useRef } from "react"; +import { Skeleton } from "@/components/ui/skeleton"; +import { TrendingUp, PhoneCall, CalendarDays } from "lucide-react"; +import { format } from "date-fns"; + +function formatNumber(num) { + return num?.toLocaleString() ?? '0'; +} + +const tickerStyles = { + blue: 'bg-blue-600/20 border-blue-600/30 text-blue-400 group-hover:bg-blue-600/30', + green: 'bg-green-600/20 border-green-600/30 text-green-400 group-hover:bg-green-600/30', + purple: 'bg-purple-600/20 border-purple-600/30 text-purple-400 group-hover:bg-purple-600/30', +}; + +function useAnimatedNumber(target, duration = 1200, shouldAnimate = true) { + const [display, setDisplay] = useState(0); + const rafRef = useRef(); + + useEffect(() => { + if (!shouldAnimate || typeof target !== "number") { + setDisplay(target || 0); + return; + } + let start = null; + let from = 0; + let to = target; + if (from === to) { + setDisplay(to); + return; + } + + function tick(ts) { + if (!start) start = ts; + const progress = Math.min((ts - start) / duration, 1); + const value = Math.round(from + (to - from) * progress); + setDisplay(value); + if (progress < 1) { + rafRef.current = requestAnimationFrame(tick); + } + } + rafRef.current = requestAnimationFrame(tick); + return () => rafRef.current && cancelAnimationFrame(rafRef.current); + // eslint-disable-next-line + }, [target, shouldAnimate]); + return display; +} + +function AnimatedNumber({ value, animate, ...props }) { + const animated = useAnimatedNumber(typeof value === "number" ? value : 0, 1200, animate); + return {animated.toLocaleString()}; +} + +function formatMonthLabel(monthStr) { + // monthStr is like "2025-07" + const [year, month] = monthStr.split("-"); + if (!year || !month) return monthStr; + const date = new Date(Number(year), Number(month) - 1, 1); + return format(date, "MMMM yyyy"); +} + +function formatRecordDate(dateStr) { + // dateStr is like "2024-10-28" + if (!dateStr) return ""; + const [year, month, day] = dateStr.split("-"); + if (!year || !month || !day) return dateStr; + const date = new Date(Number(year), Number(month) - 1, Number(day)); + return format(date, "MMMM do, yyyy"); +} + +function TickerCard({ color, icon, title, value, subtitle, loading, animate, children }) { + return ( +
+ {/* Animated background gradient */} +
+
+
+ {icon} +
+
{title}
+ {loading ? ( + + ) : ( + value !== undefined && value !== "" && value !== null ? ( + + ) : null + )} + {subtitle && {subtitle}} + {children} +
+ {/* Hover glow effect */} +
+
+ ); +} + +export default function HomeTickers() { + const [stats, setStats] = useState(null); + const [loading, setLoading] = useState(true); + const [animateNumbers, setAnimateNumbers] = useState(false); + const sectionRef = useRef(); + + useEffect(() => { + async function fetchStats() { + setLoading(true); + try { + const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/system/records`); + const data = await res.json(); + setStats(data.records); + } catch (err) { + setStats(null); + } finally { + setLoading(false); + } + } + fetchStats(); + }, []); + + useEffect(() => { + const ref = sectionRef.current; + if (!ref) return; + let observer; + if (typeof window !== "undefined" && "IntersectionObserver" in window) { + observer = new window.IntersectionObserver( + ([entry]) => { + if (entry.isIntersecting) { + setAnimateNumbers(true); + } + }, + { threshold: 0.4 } + ); + observer.observe(ref); + } + return () => observer && observer.disconnect(); + }, []); + + const monthlyStats = stats + ? Object.entries(stats) + .filter(([k, v]) => k.startsWith("monthly_total_") && v > 0) + .sort(([a], [b]) => a.localeCompare(b)) + : []; + + // Get current month in YYYY-MM format + const now = new Date(); + const currentMonthKey = `monthly_total_${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`; + const currentMonthCount = stats?.[currentMonthKey]; + + return ( +
+
+

+ Network Activity +

+

+ Real-time call statistics from the LiteNet PBX. +

+
+
+ } + title="This Month's Calls" + value={currentMonthCount} + subtitle={format(now, "MMMM yyyy")} + loading={loading} + animate={animateNumbers} + /> + } + title="Total Calls" + value={stats?.total_calls_ever_placed} + subtitle="Since launch" + loading={loading} + animate={animateNumbers} + /> + } + title="Single-Day Call Record" + value={stats?.record_calls?.count} + subtitle={ + stats?.record_calls?.date + ? `Record set on ${formatRecordDate(stats.record_calls.date)}` + : "" + } + loading={loading} + animate={animateNumbers} + /> +
+ +
+ ); +}