/** * Template: Dashboard Widgets * * Reusable dashboard components for real-time data visualization. * Features: Gauges, charts, terminals, stat cards, activity feeds */ import React, { useState, useEffect, useRef, useMemo } from 'react'; import { motion } from 'framer-motion'; // ============================================================================= // TYPES // ============================================================================= interface StatCardProps { label: string; value: string | number; icon?: React.ReactNode; trend?: number; color?: string; sparkline?: number[]; } interface ArcGaugeProps { value: number; max: number; label: string; unit?: string; color?: string; size?: 'sm' | 'md' | 'lg'; } interface LineTraceProps { data: number[]; color: string; label: string; unit?: string; height?: number; } interface LogEntry { id: number | string; timestamp: string; level: 'info' | 'warn' | 'error' | 'success'; message: string; } interface TerminalProps { logs: LogEntry[]; title?: string; maxHeight?: number; } interface MetricBarProps { label: string; value: number; max?: number; color?: string; } interface ActivityItem { id: string | number; title: string; description?: string; timestamp: string; status?: 'success' | 'warning' | 'error' | 'pending'; } // ============================================================================= // CONSTANTS // ============================================================================= const COLORS = { glass: 'rgba(255,255,255,0.05)', border: 'rgba(255,255,255,0.1)', primary: '#ff4d00', secondary: '#00f3ff', success: '#22c55e', warning: '#eab308', error: '#ef4444', info: '#3b82f6', }; // ============================================================================= // STAT CARD // ============================================================================= export const StatCard: React.FC = ({ label, value, icon, trend, color = COLORS.primary, sparkline, }) => { // Generate mini sparkline SVG const sparklinePath = useMemo(() => { if (!sparkline || sparkline.length < 2) return null; const width = 60; const height = 24; const max = Math.max(...sparkline); const min = Math.min(...sparkline); const range = max - min || 1; const points = sparkline.map((val, i) => { const x = (i / (sparkline.length - 1)) * width; const y = height - ((val - min) / range) * height; return `${x},${y}`; }).join(' '); return points; }, [sparkline]); return (
{icon && (
{icon}
)} {trend !== undefined && ( = 0 ? 'text-green-400' : 'text-red-400' }`} > {trend >= 0 ? '↑' : '↓'} {Math.abs(trend)}% )}
{value}
{label}
{sparklinePath && ( )}
); }; // ============================================================================= // ARC GAUGE // ============================================================================= export const ArcGauge: React.FC = ({ value, max, label, unit = '', color = COLORS.primary, size = 'md', }) => { const percentage = Math.min((value / max) * 100, 100); const sizes = { sm: { width: 100, radius: 35, strokeWidth: 6, fontSize: 'text-xl' }, md: { width: 140, radius: 50, strokeWidth: 8, fontSize: 'text-3xl' }, lg: { width: 180, radius: 65, strokeWidth: 10, fontSize: 'text-4xl' }, }; const { width, radius, strokeWidth, fontSize } = sizes[size]; const circumference = 2 * Math.PI * radius; const arcLength = circumference * 0.75; // 270 degrees const strokeDashoffset = arcLength - (arcLength * percentage) / 100; return (
{/* Background arc */} {/* Value arc */} {/* Glow effect */}
{Math.floor(value)} {unit && {unit}} {label}
); }; // ============================================================================= // LINE TRACE CHART // ============================================================================= export const LineTrace: React.FC = ({ data, color, label, unit = '', height = 60, }) => { const width = 200; const currentValue = data[data.length - 1] || 0; const pathD = useMemo(() => { if (data.length < 2) return ''; const max = Math.max(...data, 100); const min = Math.min(...data, 0); const range = max - min || 1; return data.map((val, i) => { const x = (i / (data.length - 1)) * width; const y = height - ((val - min) / range) * height; return `${i === 0 ? 'M' : 'L'} ${x} ${y}`; }).join(' '); }, [data, height]); // Create gradient fill path const fillPath = useMemo(() => { if (!pathD) return ''; return `${pathD} L ${width} ${height} L 0 ${height} Z`; }, [pathD, height]); return (
{label} {currentValue.toFixed(1)}{unit}
{/* Fill */} {/* Line */}
); }; // ============================================================================= // TERMINAL / LOG VIEWER // ============================================================================= export const Terminal: React.FC = ({ logs, title = 'System Logs', maxHeight = 300, }) => { const scrollRef = useRef(null); // Auto-scroll to bottom useEffect(() => { if (scrollRef.current) { scrollRef.current.scrollTop = scrollRef.current.scrollHeight; } }, [logs]); const levelColors = { info: COLORS.secondary, warn: COLORS.warning, error: COLORS.error, success: COLORS.success, }; return (
{/* Header */}
{title}
{/* Logs */}
{logs.map((log) => (
{log.timestamp} [{log.level.toUpperCase()}] {log.message}
))} {/* Cursor */}
); }; // ============================================================================= // METRIC BAR // ============================================================================= export const MetricBar: React.FC = ({ label, value, max = 100, color = COLORS.primary, }) => { const percentage = Math.min((value / max) * 100, 100); return (
{label} {value}%
); }; // ============================================================================= // ACTIVITY FEED // ============================================================================= export const ActivityFeed: React.FC<{ items: ActivityItem[] }> = ({ items }) => { const statusColors = { success: COLORS.success, warning: COLORS.warning, error: COLORS.error, pending: COLORS.info, }; return (
{items.map((item, index) => ( {item.status && ( )}
{item.title}
{item.description && (
{item.description}
)}
{item.timestamp}
))}
); }; // ============================================================================= // BENTO GRID LAYOUT // ============================================================================= interface BentoGridProps { children: React.ReactNode; columns?: number; gap?: number; } export const BentoGrid: React.FC = ({ children, columns = 4, gap = 16, }) => (
{children}
); interface BentoItemProps { children: React.ReactNode; colSpan?: number; rowSpan?: number; className?: string; } export const BentoItem: React.FC = ({ children, colSpan = 1, rowSpan = 1, className = '', }) => (
{children}
); // ============================================================================= // HOOKS FOR DATA SIMULATION // ============================================================================= /** * Hook for simulating telemetry data */ export const useSimulatedMetrics = (initialValues: Record) => { const [metrics, setMetrics] = useState(initialValues); useEffect(() => { const interval = setInterval(() => { setMetrics(prev => { const newMetrics: Record = {}; for (const key in prev) { const change = (Math.random() - 0.5) * 10; newMetrics[key] = Math.max(0, Math.min(100, prev[key] + change)); } return newMetrics; }); }, 1000); return () => clearInterval(interval); }, []); return metrics; }; /** * Hook for maintaining rolling data history */ export const useRollingHistory = (value: number, maxLength = 50) => { const [history, setHistory] = useState(Array(maxLength).fill(value)); useEffect(() => { setHistory(prev => [...prev.slice(1), value]); }, [value]); return history; }; /** * Hook for simulating log feed */ export const useLogFeed = (messages: Record, interval = 3000) => { const [logs, setLogs] = useState([]); useEffect(() => { const generateLog = () => { const levels: LogEntry['level'][] = ['info', 'info', 'info', 'warn', 'success']; const level = levels[Math.floor(Math.random() * levels.length)]; const levelMessages = messages[level] || messages.info || ['System event']; const message = levelMessages[Math.floor(Math.random() * levelMessages.length)]; return { id: Date.now(), timestamp: new Date().toLocaleTimeString(), level, message, }; }; const timer = setInterval(() => { setLogs(prev => [...prev.slice(-50), generateLog()]); }, interval); return () => clearInterval(timer); }, [messages, interval]); return logs; }; // ============================================================================= // EXPORTS // ============================================================================= export default { StatCard, ArcGauge, LineTrace, Terminal, MetricBar, ActivityFeed, BentoGrid, BentoItem, useSimulatedMetrics, useRollingHistory, useLogFeed, };