"use client"; import { CheckCircle, Clock, Euro, Globe, LogOut, MessageCircle, MessageSquare, MoreVertical, RefreshCw, TrendingUp, Users, Zap, } from "lucide-react"; import { useRouter } from "next/navigation"; import { signOut, useSession } from "next-auth/react"; import { useCallback, useEffect, useId, useState } from "react"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Skeleton } from "@/components/ui/skeleton"; import { formatEnumValue } from "@/lib/format-enums"; import { trpc } from "@/lib/trpc-client"; import ModernBarChart from "../../../components/charts/bar-chart"; import ModernDonutChart from "../../../components/charts/donut-chart"; import ModernLineChart from "../../../components/charts/line-chart"; import GeographicMap from "../../../components/GeographicMap"; import ResponseTimeDistribution from "../../../components/ResponseTimeDistribution"; import TopQuestionsChart from "../../../components/TopQuestionsChart"; import MetricCard from "../../../components/ui/metric-card"; import WordCloud from "../../../components/WordCloud"; import type { Company, MetricsResult, WordCloudWord } from "../../../lib/types"; /** * Loading states component for better organization */ function DashboardLoadingStates({ status }: { status: string }) { if (status === "loading") { return (

Loading session...

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

Redirecting to login...

); } return null; } /** * Loading skeleton component */ function DashboardSkeleton() { return (
{/* Header Skeleton */}
{/* Metrics Grid Skeleton */}
{Array.from({ length: 8 }, (_, i) => { const metricTypes = [ "sessions", "users", "time", "response", "costs", "peak", "resolution", "languages", ]; return ( ); })}
{/* Charts Skeleton */}
); } /** * Data processing utilities */ function useDashboardData(metrics: MetricsResult | null) { const getSentimentData = useCallback(() => { 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))", }, ]; }, [metrics]); const getSessionsOverTimeData = useCallback(() => { 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, })); }, [metrics?.days]); const getCategoriesData = useCallback(() => { if (!metrics?.categories) return []; return Object.entries(metrics.categories).map(([name, value]) => { const formattedName = formatEnumValue(name) || name; return { name: formattedName.length > 15 ? `${formattedName.substring(0, 15)}...` : formattedName, value: value as number, }; }); }, [metrics?.categories]); const getLanguagesData = useCallback(() => { if (!metrics?.languages) return []; return Object.entries(metrics.languages).map(([name, value]) => ({ name, value: value as number, })); }, [metrics?.languages]); const getWordCloudData = useCallback((): WordCloudWord[] => { if (!metrics?.wordCloudData) return []; return metrics.wordCloudData; }, [metrics?.wordCloudData]); const getCountryData = useCallback(() => { if (!metrics?.countries) return {}; return Object.entries(metrics.countries).reduce( (acc, [code, count]) => { if (code && count) { acc[code] = count; } return acc; }, {} as Record ); }, [metrics?.countries]); const getResponseTimeData = useCallback(() => { 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; }, [metrics?.avgResponseTime]); return { getSentimentData, getSessionsOverTimeData, getCategoriesData, getLanguagesData, getWordCloudData, getCountryData, getResponseTimeData, }; } /** * Dashboard header component */ function DashboardHeader({ company, metrics, isAuditor, refreshing, onRefresh, }: { company: Company; metrics: MetricsResult; isAuditor: boolean; refreshing: boolean; onRefresh: () => void; }) { const refreshStatusId = useId(); return (

{company.name}

Analytics Dashboard

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

{refreshing && (
Dashboard data is being refreshed
)} signOut({ callbackUrl: "/login" })} >
); } /** * Individual metric card components for better organization */ function SessionMetricCard({ metrics }: { metrics: MetricsResult }) { return ( } trend={{ value: metrics.sessionTrend ?? 0, isPositive: (metrics.sessionTrend ?? 0) >= 0, }} variant="primary" /> ); } function UsersMetricCard({ metrics }: { metrics: MetricsResult }) { return ( } trend={{ value: metrics.usersTrend ?? 0, isPositive: (metrics.usersTrend ?? 0) >= 0, }} variant="success" /> ); } function SessionTimeMetricCard({ metrics }: { metrics: MetricsResult }) { return ( } trend={{ value: metrics.avgSessionTimeTrend ?? 0, isPositive: (metrics.avgSessionTimeTrend ?? 0) >= 0, }} /> ); } function ResponseTimeMetricCard({ metrics }: { metrics: MetricsResult }) { return ( } trend={{ value: metrics.avgResponseTimeTrend ?? 0, isPositive: (metrics.avgResponseTimeTrend ?? 0) <= 0, }} variant="warning" /> ); } function CostsMetricCard({ metrics }: { metrics: MetricsResult }) { return ( } description="Average per day" /> ); } function PeakUsageMetricCard({ metrics }: { metrics: MetricsResult }) { return ( } description="Busiest hour" /> ); } function ResolutionRateMetricCard({ metrics }: { metrics: MetricsResult }) { return ( } trend={{ value: metrics.resolvedChatsPercentage ?? 0, isPositive: (metrics.resolvedChatsPercentage ?? 0) >= 80, }} variant={ metrics.resolvedChatsPercentage && metrics.resolvedChatsPercentage >= 80 ? "success" : "warning" } /> ); } function LanguagesMetricCard({ metrics }: { metrics: MetricsResult }) { return ( } description="Languages detected" /> ); } /** * Simplified metrics grid component */ function MetricsGrid({ metrics }: { metrics: MetricsResult }) { return (
); } /** * Main dashboard content with reduced complexity */ function DashboardContent() { const { data: session, status } = useSession(); const router = useRouter(); const [metrics, setMetrics] = useState(null); // Remove unused company state that was causing skeleton view to always show const [refreshing, setRefreshing] = useState(false); const [isInitialLoad, setIsInitialLoad] = useState(true); const isAuditor = session?.user?.role === "AUDITOR"; const dataHelpers = useDashboardData(metrics); // Function to fetch metrics with optional date range // tRPC query for dashboard metrics const { data: overviewData, isLoading: isLoadingMetrics, refetch: refetchMetrics, error: metricsError, } = trpc.dashboard.getOverview.useQuery( { // Add date range parameters when implemented // startDate: dateRange?.startDate, // endDate: dateRange?.endDate, }, { enabled: status === "authenticated", } ); // Update state when data changes useEffect(() => { if (overviewData) { // Map overview data to metrics format expected by the component const mappedMetrics: Partial = { totalSessions: overviewData.totalSessions, avgSessionsPerDay: overviewData.avgSessionsPerDay || 0, avgSessionLength: overviewData.avgSessionLength || 0, days: overviewData.timeSeriesData?.reduce( (acc, item) => { if (item.date) { acc[item.date] = item.sessionCount || 0; } return acc; }, {} as Record ) || {}, languages: overviewData.languageDistribution?.reduce( (acc, item) => { if (item.language) { acc[item.language] = item.count; } return acc; }, {} as Record ) || {}, countries: overviewData.geographicDistribution?.reduce( (acc, item) => { if (item.country) { acc[item.country] = item.count; } return acc; }, {} as Record ) || {}, belowThresholdCount: overviewData.belowThresholdCount || 0, // Map sentiment data to individual counts sentimentPositiveCount: overviewData.sentimentDistribution?.find( (s) => s.sentiment === "POSITIVE" )?.count || 0, sentimentNeutralCount: overviewData.sentimentDistribution?.find( (s) => s.sentiment === "NEUTRAL" )?.count || 0, sentimentNegativeCount: overviewData.sentimentDistribution?.find( (s) => s.sentiment === "NEGATIVE" )?.count || 0, // Map category data to CategoryMetrics format ...(overviewData.categoryDistribution && { categories: overviewData.categoryDistribution.reduce( (acc, item) => { if (item.category) { acc[item.category] = item.count; } return acc; }, {} as Record ), }), }; setMetrics(mappedMetrics as MetricsResult); if (isInitialLoad) { setIsInitialLoad(false); } } }, [overviewData, isInitialLoad]); // Admin refresh sessions mutation const refreshSessionsMutation = trpc.admin.refreshSessions.useMutation({ onSuccess: () => { // Refetch metrics after successful refresh refetchMetrics(); }, onError: (error) => { alert(`Failed to refresh sessions: ${error.message}`); }, }); useEffect(() => { // Redirect if not authenticated if (status === "unauthenticated") { router.push("/login"); return; } // tRPC queries handle data fetching automatically }, [status, router]); // Enhanced error handling with user feedback if (metricsError) { return (
Failed to load dashboard data

There was an error loading your dashboard metrics. Please try refreshing the page.

); } async function handleRefresh() { if (isAuditor) return; setRefreshing(true); try { await refreshSessionsMutation.mutateAsync(); } finally { setRefreshing(false); } } // Show loading state while session status is being determined const loadingState = DashboardLoadingStates({ status }); if (loadingState) return loadingState; // Show loading state while data is being fetched if (isLoadingMetrics && !metrics) { return (

Loading dashboard data...

); } if (!metrics) { return ; } return (
{/* Date Range Picker */} {/* {dateRange && ( )} */} {/* 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 ; }