import { NextResponse, NextRequest } from "next/server"; import { createServiceClient } from "@/lib/supabase"; import { requireAdmin } from "@/lib/admin-auth"; export const dynamic = "force-dynamic"; type DateRange = "today" | "7days" | "30days"; function getDateRange(range: DateRange): { start: string; end: string } { const end = new Date(); const start = new Date(); switch (range) { case "today": start.setHours(0, 0, 0, 0); break; case "7days": start.setDate(start.getDate() - 7); break; case "30days": start.setDate(start.getDate() - 30); break; } return { start: start.toISOString(), end: end.toISOString(), }; } export async function GET(request: NextRequest) { // Admin Check const check = await requireAdmin(); if (check instanceof NextResponse) return check; const searchParams = request.nextUrl.searchParams; const range = (searchParams.get("range") || "today") as DateRange; const { start, end } = getDateRange(range); try { const db = createServiceClient(); // ─── 1. KPIs: Calls today, trend, unique numbers ──────────────────────── // Calls today const todayStart = new Date(); todayStart.setHours(0, 0, 0, 0); const todayISO = todayStart.toISOString(); const { count: todayCount } = await db .from("phone_clicks") .select("*", { count: "exact" }) .gte("timestamp", todayISO) .lte("timestamp", new Date().toISOString()); const callsToday = todayCount || 0; // Calls yesterday for trend const yesterdayStart = new Date(todayStart); yesterdayStart.setDate(yesterdayStart.getDate() - 1); const yesterdayEnd = new Date(yesterdayStart); yesterdayEnd.setDate(yesterdayEnd.getDate() + 1); const yesterdayISO = yesterdayStart.toISOString(); const yesterdayEndISO = yesterdayEnd.toISOString(); const { count: yesterdayCount } = await db .from("phone_clicks") .select("*", { count: "exact" }) .gte("timestamp", yesterdayISO) .lt("timestamp", yesterdayEndISO); const callsYesterday = yesterdayCount || 0; const callsTodayTrend = callsYesterday > 0 ? Math.round(((callsToday - callsYesterday) / callsYesterday) * 100) : callsToday > 0 ? 100 : 0; // Unique Numbers const { data: uniqueData } = await db .from("phone_clicks") .select("phone_number") .gte("timestamp", start) .lte("timestamp", end); const uniqueNumbers = new Set( (uniqueData || []).map((d) => d.phone_number) ).size; // Top Number const numberCounts: Record = {}; (uniqueData || []).forEach((item) => { numberCounts[item.phone_number] = (numberCounts[item.phone_number] || 0) + 1; }); const topNumberEntries = Object.entries(numberCounts).sort( ([, a], [, b]) => b - a ); const [topNumber, topNumberCount] = topNumberEntries[0] || [null, 0]; // Top Source Element const { data: elementData } = await db .from("phone_clicks") .select("source_element") .gte("timestamp", start) .lte("timestamp", end); const elementCounts: Record = {}; (elementData || []).forEach((item) => { elementCounts[item.source_element] = (elementCounts[item.source_element] || 0) + 1; }); const totalClicks = elementData?.length || 0; const elementEntries = Object.entries(elementCounts).sort( ([, a], [, b]) => b - a ); const [topSourceElement, topSourceElementCount] = elementEntries[0] || [ null, 0, ]; const topSourceElementPercent = totalClicks > 0 ? Math.round((topSourceElementCount / totalClicks) * 100) : 0; // ─── 2. Phone Numbers Table ───────────────────────────────────────────── const { data: tableData } = await db .from("phone_clicks") .select("phone_number, source_page, source_element, country") .gte("timestamp", start) .lte("timestamp", end); const phoneNumbersMap: Record< string, { phone_number: string; click_count: number; trend_percent: number; top_source_page: string; top_source_element: string; top_country: string; } > = {}; (tableData || []).forEach((item) => { if (!phoneNumbersMap[item.phone_number]) { phoneNumbersMap[item.phone_number] = { phone_number: item.phone_number, click_count: 0, trend_percent: 0, top_source_page: "", top_source_element: "", top_country: item.country || "—", }; } phoneNumbersMap[item.phone_number].click_count++; }); const phoneNumbers = Object.values(phoneNumbersMap) .sort((a, b) => b.click_count - a.click_count); // ─── 3. Element Chart ─────────────────────────────────────────────────── const elements = Object.entries(elementCounts) .map(([element, count]) => ({ source_element: element, count, percent: totalClicks > 0 ? Math.round((count / totalClicks) * 100) : 0, })) .sort((a, b) => b.count - a.count); // ─── 4. Timeseries Chart ──────────────────────────────────────────────── const { data: timeseriesRaw } = await db .from("phone_clicks") .select("timestamp") .gte("timestamp", start) .lte("timestamp", end) .order("timestamp", { ascending: true }); const timeseriesMap: Record = {}; (timeseriesRaw || []).forEach((item) => { const date = item.timestamp.split("T")[0]; timeseriesMap[date] = (timeseriesMap[date] || 0) + 1; }); const timeseries = Object.entries(timeseriesMap).map(([date, count]) => ({ date, count, })); // ─── 5. Geo Chart ─────────────────────────────────────────────────────── const { data: geoRaw } = await db .from("phone_clicks") .select("country") .gte("timestamp", start) .lte("timestamp", end) .not("country", "is", null); const geoCounts: Record = {}; (geoRaw || []).forEach((item) => { if (item.country) { geoCounts[item.country] = (geoCounts[item.country] || 0) + 1; } }); const geoTotal = geoRaw?.length || 0; const geo = Object.entries(geoCounts) .map(([country, count]) => ({ country, count, percent: geoTotal > 0 ? Math.round((count / geoTotal) * 100) : 0, })) .sort((a, b) => b.count - a.count) .slice(0, 10); return NextResponse.json({ kpis: { callsToday, callsTodayTrend, uniqueNumbers, topNumber, topNumberCount, topSourceElement, topSourceElementPercent, }, phoneNumbers, elements, timeseries, geo, }); } catch (error) { console.error("Phone calls analytics error:", error); return NextResponse.json( { error: "Internal server error" }, { status: 500 } ); } }