"use client"; import { useEffect, useState, useCallback, useRef } from "react"; import { signOut, useSession } from "next-auth/react"; import { useRouter } from "next/navigation"; import { Company, MetricsResult, WordCloudWord } from "../../../lib/types"; import MetricCard from "../../../components/ui/metric-card"; import ModernLineChart from "../../../components/charts/line-chart"; import ModernBarChart from "../../../components/charts/bar-chart"; import ModernDonutChart from "../../../components/charts/donut-chart"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Skeleton } from "@/components/ui/skeleton"; import { Separator } from "@/components/ui/separator"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { MessageSquare, Users, Clock, Zap, Euro, TrendingUp, CheckCircle, RefreshCw, LogOut, Calendar, MoreVertical, Globe, MessageCircle, } from "lucide-react"; import WordCloud from "../../../components/WordCloud"; import GeographicMap from "../../../components/GeographicMap"; import ResponseTimeDistribution from "../../../components/ResponseTimeDistribution"; import DateRangePicker from "../../../components/DateRangePicker"; import TopQuestionsChart from "../../../components/TopQuestionsChart"; // Safely wrapped component with useSession function DashboardContent() { const { data: session, status } = useSession(); const router = useRouter(); const [metrics, setMetrics] = useState(null); const [company, setCompany] = useState(null); const [loading, setLoading] = useState(false); const [refreshing, setRefreshing] = useState(false); const [dateRange, setDateRange] = useState<{ minDate: string; maxDate: string } | null>(null); const [selectedStartDate, setSelectedStartDate] = useState(""); const [selectedEndDate, setSelectedEndDate] = useState(""); const [isInitialLoad, setIsInitialLoad] = useState(true); const isAuditor = session?.user?.role === "AUDITOR"; // Function to fetch metrics with optional date range const fetchMetrics = async (startDate?: string, endDate?: string, isInitial = false) => { setLoading(true); try { let url = "/api/dashboard/metrics"; if (startDate && endDate) { url += `?startDate=${startDate}&endDate=${endDate}`; } const res = await fetch(url); const data = await res.json(); setMetrics(data.metrics); setCompany(data.company); // Set date range from API response (only on initial load) if (data.dateRange && isInitial) { setDateRange(data.dateRange); setSelectedStartDate(data.dateRange.minDate); setSelectedEndDate(data.dateRange.maxDate); setIsInitialLoad(false); } } catch (error) { console.error("Error fetching metrics:", error); } finally { setLoading(false); } }; // Handle date range changes with proper memoization const handleDateRangeChange = useCallback((startDate: string, endDate: string) => { // Only update if dates actually changed to prevent unnecessary API calls if (startDate !== selectedStartDate || endDate !== selectedEndDate) { setSelectedStartDate(startDate); setSelectedEndDate(endDate); fetchMetrics(startDate, endDate); } }, [selectedStartDate, selectedEndDate]); useEffect(() => { // Redirect if not authenticated if (status === "unauthenticated") { router.push("/login"); return; } // Fetch metrics and company on mount if authenticated if (status === "authenticated" && isInitialLoad) { fetchMetrics(undefined, undefined, true); } }, [status, router, isInitialLoad]); async function handleRefresh() { if (isAuditor) return; try { setRefreshing(true); if (!company?.id) { setRefreshing(false); alert("Cannot refresh: Company ID is missing"); return; } const res = await fetch("/api/admin/refresh-sessions", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ companyId: company.id }), }); if (res.ok) { const metricsRes = await fetch("/api/dashboard/metrics"); const data = await metricsRes.json(); setMetrics(data.metrics); } else { const errorData = await res.json(); alert(`Failed to refresh sessions: ${errorData.error}`); } } finally { setRefreshing(false); } } // Show loading state while session status is being determined if (status === "loading") { return (

Loading session...

); } if (status === "unauthenticated") { return (

Redirecting to login...

); } if (loading || !metrics || !company) { return (
{/* Header Skeleton */}
{/* Metrics Grid Skeleton */}
{Array.from({ length: 8 }).map((_, i) => ( ))}
{/* Charts Skeleton */}
); } // Data preparation functions const getSentimentData = () => { if (!metrics) return []; const sentimentData = { positive: metrics.sentimentPositiveCount ?? 0, neutral: metrics.sentimentNeutralCount ?? 0, negative: metrics.sentimentNegativeCount ?? 0, }; return [ { name: "Positive", value: sentimentData.positive, color: "hsl(var(--chart-1))" }, { name: "Neutral", value: sentimentData.neutral, color: "hsl(var(--chart-2))" }, { name: "Negative", value: sentimentData.negative, color: "hsl(var(--chart-3))" }, ]; }; const getSessionsOverTimeData = () => { if (!metrics?.days) return []; return Object.entries(metrics.days).map(([date, value]) => ({ date: new Date(date).toLocaleDateString('en-US', { month: 'short', day: 'numeric' }), value: value as number, })); }; const getCategoriesData = () => { if (!metrics?.categories) return []; return Object.entries(metrics.categories).map(([name, value]) => ({ name: name.length > 15 ? name.substring(0, 15) + '...' : name, value: value as number, })); }; const getLanguagesData = () => { if (!metrics?.languages) return []; return Object.entries(metrics.languages).map(([name, value]) => ({ name, value: value as number, })); }; const getWordCloudData = (): WordCloudWord[] => { if (!metrics?.wordCloudData) return []; return metrics.wordCloudData; }; const getCountryData = () => { if (!metrics?.countries) return {}; return Object.entries(metrics.countries).reduce( (acc, [code, count]) => { if (code && count) { acc[code] = count; } return acc; }, {} as Record ); }; const getResponseTimeData = () => { const avgTime = metrics.avgResponseTime || 1.5; const simulatedData: number[] = []; for (let i = 0; i < 50; i++) { const randomFactor = 0.5 + Math.random(); simulatedData.push(avgTime * randomFactor); } return simulatedData; }; return (
{/* Modern Header */}

{company.name}

Analytics Dashboard

Last updated{" "} {new Date(metrics.lastUpdated || Date.now()).toLocaleString()}

signOut({ callbackUrl: "/login" })}> Sign out
{/* Date Range Picker */} {dateRange && ( )} {/* Modern Metrics Grid */}
} trend={{ value: metrics.sessionTrend ?? 0, isPositive: (metrics.sessionTrend ?? 0) >= 0, }} variant="primary" /> } trend={{ value: metrics.usersTrend ?? 0, isPositive: (metrics.usersTrend ?? 0) >= 0, }} variant="success" /> } trend={{ value: metrics.avgSessionTimeTrend ?? 0, isPositive: (metrics.avgSessionTimeTrend ?? 0) >= 0, }} /> } trend={{ value: metrics.avgResponseTimeTrend ?? 0, isPositive: (metrics.avgResponseTimeTrend ?? 0) <= 0, }} variant="warning" /> } description="Average per day" /> } description="Busiest hour" /> } trend={{ value: metrics.resolvedChatsPercentage ?? 0, isPositive: (metrics.resolvedChatsPercentage ?? 0) >= 80, }} variant={metrics.resolvedChatsPercentage && metrics.resolvedChatsPercentage >= 80 ? "success" : "warning"} /> } description="Languages detected" />
{/* Charts Section */}
{/* Geographic and Topics Section */}
Geographic Distribution Common Topics
{/* Top Questions Chart */} {/* Response Time Distribution */} Response Time Distribution {/* Token Usage Summary */}
AI Usage & Costs
Total Tokens: {metrics.totalTokens?.toLocaleString() || 0} Total Cost: €{metrics.totalTokensEur?.toFixed(4) || 0}

Token usage chart will be implemented with historical data

); } export default function DashboardPage() { return ; }