"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 (
);
}
if (status === "unauthenticated") {
return (
);
}
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" })}
>
Sign out
);
}
/**
* 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);
const [company, setCompany] = useState(null);
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 = {
totalSessions: overviewData.totalSessions,
avgMessagesSent: overviewData.avgMessagesSent,
sentimentDistribution: overviewData.sentimentDistribution,
categoryDistribution: overviewData.categoryDistribution,
};
setMetrics(mappedMetrics as any); // Type assertion for compatibility
if (isInitialLoad) {
setIsInitialLoad(false);
}
}
}, [overviewData, isInitialLoad]);
useEffect(() => {
if (metricsError) {
console.error("Error fetching metrics:", metricsError);
}
}, [metricsError]);
// 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]);
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 || !company) {
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 ;
}