"use client"; import { useEffect, useState } from "react"; import { useTenant } from "@/contexts/TenantContext"; type HeatmapData = { hour: number; day: number; // 0-6 (Sunday-Saturday) count: number; }; type ToolUsageTrend = { tool: string; count: number; trend: "up" | "down" | "stable"; }; type TenantHeatmapProps = { days?: number; }; const API_BASE = process.env.NEXT_PUBLIC_API_URL?.replace(/\/$/, "") || "http://localhost:8000"; const DAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; const HOURS = Array.from({ length: 24 }, (_, i) => i); export function TenantHeatmap({ days = 7 }: TenantHeatmapProps) { const { tenantId, role } = useTenant(); const [loading, setLoading] = useState(false); const [heatmapData, setHeatmapData] = useState([]); const [toolTrends, setToolTrends] = useState([]); const [maxCount, setMaxCount] = useState(1); useEffect(() => { async function fetchHeatmapData() { if (!tenantId) return; setLoading(true); try { // Fetch activity data const activityRes = await fetch( `${API_BASE}/analytics/activity?days=${days}`, { headers: { "x-tenant-id": tenantId, "x-user-role": role, }, }, ); if (!activityRes.ok) { throw new Error(`Activity endpoint returned ${activityRes.status}`); } const activityData = await activityRes.json(); // Fetch tool usage stats const toolRes = await fetch( `${API_BASE}/analytics/tool-usage?days=${days}`, { headers: { "x-tenant-id": tenantId, "x-user-role": role, }, }, ); if (!toolRes.ok) { throw new Error(`Tool usage endpoint returned ${toolRes.status}`); } const toolData = await toolRes.json(); // Process activity data into heatmap format const heatmap: HeatmapData[] = []; const activityByHour: Record = {}; // Group activities by hour and day if (activityData.activities && Array.isArray(activityData.activities)) { activityData.activities.forEach((activity: any) => { try { const timestamp = new Date(activity.timestamp || activity.created_at); // Check if date is valid if (!isNaN(timestamp.getTime())) { const hour = timestamp.getHours(); const day = timestamp.getDay(); const key = `${day}-${hour}`; activityByHour[key] = (activityByHour[key] || 0) + 1; } } catch (e) { // Skip invalid timestamps console.warn("Invalid timestamp in activity:", activity); } }); } // Build heatmap data for (let day = 0; day < 7; day++) { for (let hour = 0; hour < 24; hour++) { const key = `${day}-${hour}`; const count = activityByHour[key] || 0; heatmap.push({ hour, day, count }); } } setHeatmapData(heatmap); setMaxCount(Math.max(...heatmap.map((h) => h.count), 1)); // Process tool trends const trends: ToolUsageTrend[] = []; if (toolData.tool_usage) { Object.entries(toolData.tool_usage).forEach(([tool, stats]: [string, any]) => { trends.push({ tool, count: stats.count || 0, trend: "stable", // Could be calculated from historical data }); }); } // Sort by count descending trends.sort((a, b) => b.count - a.count); setToolTrends(trends.slice(0, 10)); // Top 10 tools } catch (err) { console.error("Failed to fetch heatmap data:", err); // Set empty data on error to show empty state setHeatmapData([]); setToolTrends([]); setMaxCount(1); } finally { setLoading(false); } } fetchHeatmapData(); }, [tenantId, role, days]); const getIntensity = (count: number) => { if (maxCount === 0) return 0; const ratio = count / maxCount; if (ratio === 0) return 0; if (ratio < 0.2) return 1; if (ratio < 0.4) return 2; if (ratio < 0.6) return 3; if (ratio < 0.8) return 4; return 5; }; const getColorClass = (intensity: number) => { const colors = [ "bg-slate-800/30 border-slate-700/30", // 0 "bg-cyan-500/20 border-cyan-500/30", // 1 "bg-cyan-500/40 border-cyan-500/50", // 2 "bg-cyan-500/60 border-cyan-500/70", // 3 "bg-cyan-500/80 border-cyan-500/90", // 4 "bg-cyan-500 border-cyan-400", // 5 ]; return colors[intensity] || colors[0]; }; return (
{/* Query Heatmap */}

Query Activity Heatmap

Last {days} days
{loading ? (
Loading heatmap data...
) : (
{/* Hour labels */}
{HOURS.map((hour) => (
{hour % 6 === 0 ? hour : ""}
))}
{/* Heatmap grid */}
{DAYS.map((dayName, dayIdx) => { const dayData = heatmapData.filter((d) => d.day === dayIdx); return (
{dayName}
{HOURS.map((hour) => { const data = dayData.find((d) => d.hour === hour); const count = data?.count || 0; const intensity = getIntensity(count); return (
); })}
); })}
{/* Legend */}
Less
{[0, 1, 2, 3, 4, 5].map((intensity) => (
))}
More
)}
{/* Tool Usage Trends */}

Per-Tool Usage Trends

Top 10 tools
{loading ? (
Loading tool trends...
) : toolTrends.length === 0 ? (
No tool usage data available
) : (
{toolTrends.map((trend, idx) => { const maxToolCount = Math.max(...toolTrends.map((t) => t.count), 1); const widthPercent = (trend.count / maxToolCount) * 100; return (
{trend.tool} {trend.trend === "up" && ( )} {trend.trend === "down" && ( )}
{trend.count}
); })}
)}
); }