diff --git a/app/dashboard/overview/page.tsx b/app/dashboard/overview/page.tsx index 511a14e..57beb61 100644 --- a/app/dashboard/overview/page.tsx +++ b/app/dashboard/overview/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { useEffect, useState } from "react"; +import { useEffect, useState, useCallback } from "react"; import { signOut, useSession } from "next-auth/react"; import { useRouter } from "next/navigation"; import { @@ -16,6 +16,7 @@ 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"; // Safely wrapped component with useSession function DashboardContent() { @@ -25,9 +26,47 @@ function DashboardContent() { 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") { @@ -37,23 +76,9 @@ function DashboardContent() { // Fetch metrics and company on mount if authenticated if (status === "authenticated") { - const fetchData = async () => { - setLoading(true); - const res = await fetch("/api/dashboard/metrics"); - const data = await res.json(); - console.log("Metrics from API:", { - avgSessionLength: data.metrics.avgSessionLength, - avgSessionTimeTrend: data.metrics.avgSessionTimeTrend, - totalSessionDuration: data.metrics.totalSessionDuration, - validSessionsForDuration: data.metrics.validSessionsForDuration, - }); - setMetrics(data.metrics); - setCompany(data.company); - setLoading(false); - }; - fetchData(); + fetchMetrics(); } - }, [status, router]); // Add status and router to dependency array + }, [status, router, fetchMetrics]); // Add fetchMetrics to dependency array async function handleRefresh() { if (isAuditor) return; // Prevent auditors from refreshing @@ -231,6 +256,18 @@ function DashboardContent() { + + {/* Date Range Picker */} + {dateRange && ( + + )} +
void; + initialStartDate?: string; + initialEndDate?: string; +} + +export default function DateRangePicker({ + minDate, + maxDate, + onDateRangeChange, + initialStartDate, + initialEndDate, +}: DateRangePickerProps) { + const [startDate, setStartDate] = useState(initialStartDate || minDate); + const [endDate, setEndDate] = useState(initialEndDate || maxDate); + + useEffect(() => { + // Notify parent component when dates change + onDateRangeChange(startDate, endDate); + }, [startDate, endDate, onDateRangeChange]); + + const handleStartDateChange = (newStartDate: string) => { + // Ensure start date is not before min date + if (newStartDate < minDate) { + setStartDate(minDate); + return; + } + + // Ensure start date is not after end date + if (newStartDate > endDate) { + setEndDate(newStartDate); + } + + setStartDate(newStartDate); + }; + + const handleEndDateChange = (newEndDate: string) => { + // Ensure end date is not after max date + if (newEndDate > maxDate) { + setEndDate(maxDate); + return; + } + + // Ensure end date is not before start date + if (newEndDate < startDate) { + setStartDate(newEndDate); + } + + setEndDate(newEndDate); + }; + + const resetToFullRange = () => { + setStartDate(minDate); + setEndDate(maxDate); + }; + + const setLast30Days = () => { + const thirtyDaysAgo = new Date(); + thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30); + const thirtyDaysAgoStr = thirtyDaysAgo.toISOString().split('T')[0]; + + // Use the later of 30 days ago or minDate + const newStartDate = thirtyDaysAgoStr > minDate ? thirtyDaysAgoStr : minDate; + setStartDate(newStartDate); + setEndDate(maxDate); + }; + + const setLast7Days = () => { + const sevenDaysAgo = new Date(); + sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7); + const sevenDaysAgoStr = sevenDaysAgo.toISOString().split('T')[0]; + + // Use the later of 7 days ago or minDate + const newStartDate = sevenDaysAgoStr > minDate ? sevenDaysAgoStr : minDate; + setStartDate(newStartDate); + setEndDate(maxDate); + }; + + return ( +
+
+
+ + +
+
+ + handleStartDateChange(e.target.value)} + className="px-3 py-1.5 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-sky-500 focus:border-sky-500" + /> +
+ +
+ + handleEndDateChange(e.target.value)} + className="px-3 py-1.5 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-sky-500 focus:border-sky-500" + /> +
+
+
+ +
+ + + +
+
+ +
+ Available data: {new Date(minDate).toLocaleDateString()} - {new Date(maxDate).toLocaleDateString()} +
+
+ ); +} diff --git a/pages/api/dashboard/metrics.ts b/pages/api/dashboard/metrics.ts index c34a84c..487f0a8 100644 --- a/pages/api/dashboard/metrics.ts +++ b/pages/api/dashboard/metrics.ts @@ -33,8 +33,21 @@ export default async function handler( if (!user) return res.status(401).json({ error: "No user" }); + // Get date range from query parameters + const { startDate, endDate } = req.query; + + // Build where clause with optional date filtering + const whereClause: any = { companyId: user.companyId }; + + if (startDate && endDate) { + whereClause.startTime = { + gte: new Date(startDate as string), + lte: new Date(endDate as string + 'T23:59:59.999Z'), // Include full end date + }; + } + const prismaSessions = await prisma.session.findMany({ - where: { companyId: user.companyId }, + where: whereClause, }); // Convert Prisma sessions to ChatSession[] type for sessionMetrics @@ -44,7 +57,7 @@ export default async function handler( companyId: ps.companyId, startTime: new Date(ps.startTime), // Ensure startTime is a Date object endTime: ps.endTime ? new Date(ps.endTime) : null, // Ensure endTime is a Date object or null - transcriptContent: ps.transcriptContent || "", // Ensure transcriptContent is a string + transcriptContent: "", // Session model doesn't have transcriptContent field createdAt: new Date(ps.createdAt), // Map Prisma's createdAt updatedAt: new Date(ps.createdAt), // Use createdAt for updatedAt as Session model doesn't have updatedAt category: ps.category || undefined, @@ -75,9 +88,20 @@ export default async function handler( const metrics = sessionMetrics(chatSessions, companyConfigForMetrics); + // Calculate date range from sessions + let dateRange: { minDate: string; maxDate: string } | null = null; + if (prismaSessions.length > 0) { + const dates = prismaSessions.map(s => new Date(s.startTime)).sort((a, b) => a.getTime() - b.getTime()); + dateRange = { + minDate: dates[0].toISOString().split('T')[0], // First session date + maxDate: dates[dates.length - 1].toISOString().split('T')[0] // Last session date + }; + } + res.json({ metrics, csvUrl: user.company.csvUrl, company: user.company, + dateRange, }); }