MBO-Tech-IT-Webseite/app/api/admin/analytics/phone-calls/route.ts

134 lines
4.4 KiB
TypeScript

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) {
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();
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;
const yesterdayStart = new Date(todayStart);
yesterdayStart.setDate(yesterdayStart.getDate() - 1);
const yesterdayEnd = new Date(yesterdayStart);
yesterdayEnd.setDate(yesterdayEnd.getDate() + 1);
const { count: yesterdayCount } = await db
.from("phone_clicks")
.select("*", { count: "exact" })
.gte("timestamp", yesterdayStart.toISOString())
.lt("timestamp", yesterdayEnd.toISOString());
const callsYesterday = yesterdayCount || 0;
const callsTodayTrend =
callsYesterday > 0
? Math.round(((callsToday - callsYesterday) / callsYesterday) * 100)
: callsToday > 0 ? 100 : 0;
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;
const { data: elementData } = await db
.from("phone_clicks")
.select("source_element")
.gte("timestamp", start)
.lte("timestamp", end);
const elementCounts: Record<string, number> = {};
(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;
const numberCounts: Record<string, number> = {};
(uniqueData || []).forEach((item) => {
numberCounts[item.phone_number] = (numberCounts[item.phone_number] || 0) + 1;
});
const phoneNumbers = Object.entries(numberCounts)
.map(([phone_number, click_count]) => ({ phone_number, click_count }))
.sort((a, b) => b.click_count - a.click_count);
const elements = Object.entries(elementCounts)
.map(([source_element, count]) => ({
source_element,
count,
percent: totalClicks > 0 ? Math.round((count / totalClicks) * 100) : 0,
}))
.sort((a, b) => b.count - a.count);
const { data: timeseriesRaw } = await db
.from("phone_clicks")
.select("timestamp")
.gte("timestamp", start)
.lte("timestamp", end)
.order("timestamp", { ascending: true });
const timeseriesMap: Record<string, number> = {};
(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 }));
return NextResponse.json({
kpis: { callsToday, callsTodayTrend, uniqueNumbers, topSourceElement, topSourceElementPercent },
phoneNumbers,
elements,
timeseries,
});
} catch (error) {
console.error("Phone calls analytics error:", error);
return NextResponse.json({ error: "Internal server error" }, { status: 500 });
}
}