"use client"; import { useEffect, useState, useCallback } from "react"; import { signOut, useSession } from "next-auth/react"; import { useRouter } from "next/navigation"; import { SessionsLineChart, CategoriesBarChart, LanguagePieChart, TokenUsageChart, } from "../../../components/Charts"; import { Company, MetricsResult, WordCloudWord } from "../../../lib/types"; import MetricCard from "../../../components/MetricCard"; import DonutChart from "../../../components/DonutChart"; import WordCloud from "../../../components/WordCloud"; import GeographicMap from "../../../components/GeographicMap"; import ResponseTimeDistribution from "../../../components/ResponseTimeDistribution"; import WelcomeBanner from "../../../components/WelcomeBanner"; import DateRangePicker from "../../../components/DateRangePicker"; import TopQuestionsChart from "../../../components/TopQuestionsChart"; // Safely wrapped component with useSession function DashboardContent() { const { data: session, status } = useSession(); // Add status from useSession const router = useRouter(); // Initialize useRouter const [metrics, setMetrics] = useState(null); const [company, setCompany] = useState(null); const [, 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 isAuditor = session?.user?.role === "auditor"; // Function to fetch metrics with optional date range const fetchMetrics = useCallback(async (startDate?: string, endDate?: string) => { 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 && !dateRange) { setDateRange(data.dateRange); setSelectedStartDate(data.dateRange.minDate); setSelectedEndDate(data.dateRange.maxDate); } } catch (error) { console.error("Error fetching metrics:", error); } finally { setLoading(false); } }, [dateRange]); // Handle date range changes const handleDateRangeChange = useCallback((startDate: string, endDate: string) => { setSelectedStartDate(startDate); setSelectedEndDate(endDate); fetchMetrics(startDate, endDate); }, [fetchMetrics]); useEffect(() => { // Redirect if not authenticated if (status === "unauthenticated") { router.push("/login"); return; // Stop further execution in this effect } // Fetch metrics and company on mount if authenticated if (status === "authenticated") { fetchMetrics(); } }, [status, router, fetchMetrics]); // Add fetchMetrics to dependency array async function handleRefresh() { if (isAuditor) return; // Prevent auditors from refreshing try { setRefreshing(true); // Make sure we have a company ID to send 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) { // Refetch metrics 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); } } // Calculate sentiment distribution const getSentimentData = () => { if (!metrics) return { positive: 0, neutral: 0, negative: 0 }; if ( metrics.sentimentPositiveCount !== undefined && metrics.sentimentNeutralCount !== undefined && metrics.sentimentNegativeCount !== undefined ) { return { positive: metrics.sentimentPositiveCount, neutral: metrics.sentimentNeutralCount, negative: metrics.sentimentNegativeCount, }; } const total = metrics.totalSessions || 1; return { positive: Math.round(total * 0.6), neutral: Math.round(total * 0.3), negative: Math.round(total * 0.1), }; }; // Prepare token usage data const getTokenData = () => { if (!metrics || !metrics.tokensByDay) { return { labels: [], values: [], costs: [] }; } const days = Object.keys(metrics.tokensByDay).sort(); const labels = days.slice(-7); const values = labels.map((day) => metrics.tokensByDay?.[day] || 0); const costs = labels.map((day) => metrics.tokensCostByDay?.[day] || 0); return { labels, values, costs }; }; // Show loading state while session status is being determined if (status === "loading") { return
Loading session...
; } // If unauthenticated and not redirected yet (should be handled by useEffect, but as a fallback) if (status === "unauthenticated") { return
Redirecting to login...
; } if (!metrics || !company) { return
Loading dashboard...
; } // Function to prepare word cloud data from metrics.wordCloudData const getWordCloudData = (): WordCloudWord[] => { if (!metrics || !metrics.wordCloudData) return []; return metrics.wordCloudData; }; // Function to prepare country data for the map using actual metrics const getCountryData = () => { if (!metrics || !metrics.countries) return {}; // Convert the countries object from metrics to the format expected by GeographicMap const result = Object.entries(metrics.countries).reduce( (acc, [code, count]) => { if (code && count) { acc[code] = count; } return acc; }, {} as Record ); return result; }; // Function to prepare response time distribution data 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 (

{company.name}

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

{/* Date Range Picker */} {dateRange && ( )}
} trend={{ value: metrics.sessionTrend ?? 0, isPositive: (metrics.sessionTrend ?? 0) >= 0, }} /> } trend={{ value: metrics.usersTrend ?? 0, isPositive: (metrics.usersTrend ?? 0) >= 0, }} /> } trend={{ value: metrics.avgSessionTimeTrend ?? 0, isPositive: (metrics.avgSessionTimeTrend ?? 0) >= 0, }} /> } trend={{ value: metrics.avgResponseTimeTrend ?? 0, isPositive: (metrics.avgResponseTimeTrend ?? 0) <= 0, // Lower response time is better }} /> } /> } /> } trend={{ value: metrics.resolvedChatsPercentage ?? 0, isPositive: (metrics.resolvedChatsPercentage ?? 0) >= 80, // 80%+ resolution rate is good }} />

Sessions Over Time

Conversation Sentiment

Sessions by Category

Languages Used

Geographic Distribution

Common Topics

{/* Top Questions Chart */}

Response Time Distribution

Token Usage & Costs

Total Tokens: {metrics.totalTokens?.toLocaleString() || 0}
Total Cost:€ {metrics.totalTokensEur?.toFixed(4) || 0}
); } // Our exported component export default function DashboardPage() { return ; }