134 lines
4.4 KiB
TypeScript
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 });
|
|
}
|
|
}
|