import { db } from "@/db"; import { forms, submissions } from "@/db/schema"; import { desc, eq, sql } from "drizzle-orm"; import { max } from "drizzle-orm/sql/functions/aggregate"; export type DashboardDateWindows = { todayStart: Date; tomorrowStart: Date; last7DaysStart: Date; }; export type AdminDashboardData = { summary: { totalForms: number; openForms: number; closedForms: number; totalResponses: number; responsesToday: number; responsesLast7Days: number; }; recentSubmissions: { id: string; formId: string; formTitle: string; userName: string | null; userDiscordId: string | null; submittedAt: Date; edited: boolean; }[]; activeForms: { id: string; title: string; isOpen: boolean; totalResponses: number; responsesToday: number; responsesLast7Days: number; lastResponseAt: Date | null; }[]; }; export function getDashboardDateWindows(now = new Date()): DashboardDateWindows { const bangkokDate = new Intl.DateTimeFormat("en-CA", { timeZone: "Asia/Bangkok", year: "numeric", month: "2-digit", day: "2-digit", }).format(now); const todayStart = new Date(`${bangkokDate}T00:00:00+07:00`); const tomorrowStart = new Date(todayStart); tomorrowStart.setUTCDate(tomorrowStart.getUTCDate() + 1); const last7DaysStart = new Date(todayStart); last7DaysStart.setUTCDate(last7DaysStart.getUTCDate() - 6); return { todayStart, tomorrowStart, last7DaysStart }; } function toPostgresTimestamp(date: Date) { return date.toISOString().replace("T", " ").replace("Z", ""); } export async function getAdminDashboardData( now = new Date() ): Promise { const { todayStart, tomorrowStart, last7DaysStart } = getDashboardDateWindows(now); const nowSql = now.toISOString(); const todayStartSql = toPostgresTimestamp(todayStart); const tomorrowStartSql = toPostgresTimestamp(tomorrowStart); const last7DaysStartSql = toPostgresTimestamp(last7DaysStart); const totalResponses = sql`count(${submissions.id})::integer`; const acceptingSubmissions = sql`${forms.isOpen} and ( ${forms.closesAt} is null or ${forms.closesAt} > ${nowSql}::timestamptz )`; const responsesToday = sql`count(${submissions.id}) filter ( where ${submissions.submittedAt} >= ${todayStartSql}::timestamp and ${submissions.submittedAt} < ${tomorrowStartSql}::timestamp )::integer`; const responsesLast7Days = sql`count(${submissions.id}) filter ( where ${submissions.submittedAt} >= ${last7DaysStartSql}::timestamp and ${submissions.submittedAt} < ${tomorrowStartSql}::timestamp )::integer`; const [formSummaryRows, responseSummaryRows, recentSubmissions, activeForms] = await Promise.all([ db .select({ totalForms: sql`count(*)::integer`, openForms: sql`count(*) filter (where ${acceptingSubmissions})::integer`, closedForms: sql`count(*) filter (where not (${acceptingSubmissions}))::integer`, }) .from(forms), db .select({ totalResponses: sql`count(*)::integer`, responsesToday: sql`count(*) filter ( where ${submissions.submittedAt} >= ${todayStartSql}::timestamp and ${submissions.submittedAt} < ${tomorrowStartSql}::timestamp )::integer`, responsesLast7Days: sql`count(*) filter ( where ${submissions.submittedAt} >= ${last7DaysStartSql}::timestamp and ${submissions.submittedAt} < ${tomorrowStartSql}::timestamp )::integer`, }) .from(submissions), db .select({ id: submissions.id, formId: submissions.formId, formTitle: forms.title, userName: submissions.userName, userDiscordId: submissions.userDiscordId, submittedAt: submissions.submittedAt, edited: sql`jsonb_array_length(coalesce(${submissions.editHistory}, '[]'::jsonb)) > 0`, }) .from(submissions) .innerJoin(forms, eq(forms.id, submissions.formId)) .orderBy(desc(submissions.submittedAt), desc(submissions.id)) .limit(6), db .select({ id: forms.id, title: forms.title, isOpen: acceptingSubmissions, totalResponses, responsesToday, responsesLast7Days, lastResponseAt: max(submissions.submittedAt), }) .from(forms) .leftJoin(submissions, eq(submissions.formId, forms.id)) .groupBy(forms.id, forms.title, forms.isOpen, forms.closesAt, forms.createdAt) .orderBy( desc(responsesLast7Days), desc(totalResponses), desc(forms.createdAt), desc(forms.id) ) .limit(5), ]); const formSummary = formSummaryRows[0] ?? { totalForms: 0, openForms: 0, closedForms: 0, }; const responseSummary = responseSummaryRows[0] ?? { totalResponses: 0, responsesToday: 0, responsesLast7Days: 0, }; return { summary: { ...formSummary, ...responseSummary, }, recentSubmissions, activeForms, }; }