import { and, count, eq, gte, isNull, lte, sql, sum } from 'drizzle-orm'; import { db } from '@/lib/db'; import { interests } from '@/lib/db/schema/interests'; import { berths } from '@/lib/db/schema/berths'; import { auditLogs } from '@/lib/db/schema/system'; // ─── Types ──────────────────────────────────────────────────────────────────── export interface PipelineData { stageCounts: Record; topInterests: Array<{ id: string; clientId: string; pipelineStage: string; berthPrice: string | null; }>; generatedAt: string; } export interface RevenueData { stageRevenue: Record; totalCompleted: string; generatedAt: string; } export interface ActivityData { logs: Array<{ id: string; action: string; entityType: string; entityId: string | null; userId: string | null; createdAt: Date; }>; summary: Record; generatedAt: string; } export interface OccupancyData { statusCounts: Record; occupancyRate: number; totalBerths: number; generatedAt: string; } // ─── Pipeline ───────────────────────────────────────────────────────────────── export async function fetchPipelineData( portId: string, _params: Record, ): Promise { // Count interests per pipeline stage (non-archived) const stageCounts = await db .select({ stage: interests.pipelineStage, count: count(), }) .from(interests) .where(and(eq(interests.portId, portId), isNull(interests.archivedAt))); const stageCountMap: Record = {}; for (const row of stageCounts) { stageCountMap[row.stage] = row.count; } // Top 10 interests by berth price (via join) const topInterestsRows = await db .select({ id: interests.id, clientId: interests.clientId, pipelineStage: interests.pipelineStage, berthPrice: berths.price, }) .from(interests) .leftJoin(berths, eq(interests.berthId, berths.id)) .where(and(eq(interests.portId, portId), isNull(interests.archivedAt))) .orderBy(sql`${berths.price} DESC NULLS LAST`) .limit(10); return { stageCounts: stageCountMap, topInterests: topInterestsRows.map((r) => ({ id: r.id, clientId: r.clientId, pipelineStage: r.pipelineStage, berthPrice: r.berthPrice ? String(r.berthPrice) : null, })), generatedAt: new Date().toISOString(), }; } // ─── Revenue ────────────────────────────────────────────────────────────────── export async function fetchRevenueData( portId: string, _params: Record, ): Promise { // Sum berth prices grouped by pipeline stage const stageRevenue = await db .select({ stage: interests.pipelineStage, revenue: sum(berths.price), }) .from(interests) .leftJoin(berths, eq(interests.berthId, berths.id)) .where(and(eq(interests.portId, portId), isNull(interests.archivedAt))) .groupBy(interests.pipelineStage); const stageRevenueMap: Record = {}; for (const row of stageRevenue) { stageRevenueMap[row.stage] = row.revenue ? String(row.revenue) : '0'; } // Total revenue from completed interests const completedRevenue = await db .select({ total: sum(berths.price) }) .from(interests) .leftJoin(berths, eq(interests.berthId, berths.id)) .where( and( eq(interests.portId, portId), eq(interests.pipelineStage, 'completed'), isNull(interests.archivedAt), ), ); return { stageRevenue: stageRevenueMap, totalCompleted: completedRevenue[0]?.total ? String(completedRevenue[0].total) : '0', generatedAt: new Date().toISOString(), }; } // ─── Activity ───────────────────────────────────────────────────────────────── export async function fetchActivityData( portId: string, params: Record, ): Promise { const dateFrom = params.dateFrom as string | undefined; const dateTo = params.dateTo as string | undefined; const thirtyDaysAgo = new Date(); thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30); const fromDate = dateFrom ? new Date(dateFrom) : thirtyDaysAgo; const conditions = [ eq(auditLogs.portId, portId), gte(auditLogs.createdAt, fromDate), ]; if (dateTo) { conditions.push(lte(auditLogs.createdAt, new Date(dateTo))); } const logs = await db .select({ id: auditLogs.id, action: auditLogs.action, entityType: auditLogs.entityType, entityId: auditLogs.entityId, userId: auditLogs.userId, createdAt: auditLogs.createdAt, }) .from(auditLogs) .where(and(...conditions)) .orderBy(sql`${auditLogs.createdAt} DESC`) .limit(200); // Group by action type const summary: Record = {}; for (const log of logs) { const key = `${log.action}:${log.entityType}`; summary[key] = (summary[key] ?? 0) + 1; } return { logs, summary, generatedAt: new Date().toISOString(), }; } // ─── Occupancy ──────────────────────────────────────────────────────────────── export async function fetchOccupancyData( portId: string, _params: Record, ): Promise { const statusCounts = await db .select({ status: berths.status, count: count(), }) .from(berths) .where(eq(berths.portId, portId)) .groupBy(berths.status); const statusCountMap: Record = {}; let totalBerths = 0; for (const row of statusCounts) { statusCountMap[row.status] = row.count; totalBerths += row.count; } const occupiedCount = (statusCountMap['under_offer'] ?? 0) + (statusCountMap['sold'] ?? 0); const occupancyRate = totalBerths > 0 ? (occupiedCount / totalBerths) * 100 : 0; return { statusCounts: statusCountMap, occupancyRate: Math.round(occupancyRate * 10) / 10, totalBerths, generatedAt: new Date().toISOString(), }; }