Spaces:
Sleeping
Sleeping
| import { NextResponse } from "next/server"; | |
| import { db } from "@/lib/db"; | |
| import { deals, activities, pipelineStages } from "@/lib/db/schema"; | |
| import { auth } from "@/lib/auth"; | |
| import { eq, desc } from "drizzle-orm"; | |
| export async function GET() { | |
| const session = await auth(); | |
| if (!session?.user?.id) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); | |
| const userId = parseInt(session.user.id); | |
| const allDeals = db.select().from(deals).where(eq(deals.userId, userId)).all(); | |
| const allStages = db.select().from(pipelineStages).all(); | |
| const allActivities = db | |
| .select() | |
| .from(activities) | |
| .where(eq(activities.userId, userId)) | |
| .orderBy(desc(activities.createdAt)) | |
| .all(); | |
| const openDeals = allDeals.filter((d) => d.status === "open"); | |
| const wonDeals = allDeals.filter((d) => d.status === "won"); | |
| const lostDeals = allDeals.filter((d) => d.status === "lost"); | |
| // Summary cards | |
| const totalOpenValue = openDeals.reduce((sum, d) => sum + d.value, 0); | |
| const totalWonValue = wonDeals.reduce((sum, d) => sum + d.value, 0); | |
| const winRate = | |
| wonDeals.length + lostDeals.length > 0 | |
| ? Math.round((wonDeals.length / (wonDeals.length + lostDeals.length)) * 100) | |
| : 0; | |
| // Activities this week | |
| const weekAgo = new Date(); | |
| weekAgo.setDate(weekAgo.getDate() - 7); | |
| const weekStr = weekAgo.toISOString().split("T")[0]; | |
| const activitiesThisWeek = allActivities.filter( | |
| (a) => a.dueDate && a.dueDate >= weekStr | |
| ).length; | |
| // Deals by stage | |
| const stageMap = new Map(allStages.map((s) => [s.id, s])); | |
| const dealsByStage = allStages | |
| .sort((a, b) => a.position - b.position) | |
| .map((stage) => { | |
| const stageDeals = openDeals.filter((d) => d.stageId === stage.id); | |
| return { | |
| stage: stage.name, | |
| count: stageDeals.length, | |
| value: stageDeals.reduce((sum, d) => sum + d.value, 0), | |
| }; | |
| }); | |
| // Revenue forecast (next 6 months, weighted by stage probability) | |
| const forecast: { month: string; weighted: number; total: number }[] = []; | |
| for (let i = 0; i < 6; i++) { | |
| const date = new Date(); | |
| date.setMonth(date.getMonth() + i); | |
| const monthStr = date.toISOString().slice(0, 7); | |
| const monthLabel = date.toLocaleDateString("en-US", { month: "short", year: "numeric" }); | |
| const monthDeals = openDeals.filter( | |
| (d) => d.expectedCloseDate && d.expectedCloseDate.slice(0, 7) === monthStr | |
| ); | |
| const total = monthDeals.reduce((sum, d) => sum + d.value, 0); | |
| const weighted = monthDeals.reduce((sum, d) => { | |
| const stage = stageMap.get(d.stageId); | |
| const prob = stage ? stage.probability / 100 : 0; | |
| return sum + d.value * prob; | |
| }, 0); | |
| forecast.push({ month: monthLabel, weighted: Math.round(weighted), total: Math.round(total) }); | |
| } | |
| // Won/Lost breakdown | |
| const wonLost = [ | |
| { name: "Won", value: wonDeals.length }, | |
| { name: "Lost", value: lostDeals.length }, | |
| ]; | |
| // Recent activities | |
| const recentActivities = allActivities.slice(0, 10).map((a) => ({ | |
| id: a.id, | |
| type: a.type, | |
| subject: a.subject, | |
| dueDate: a.dueDate, | |
| isDone: a.isDone, | |
| createdAt: a.createdAt, | |
| })); | |
| return NextResponse.json({ | |
| summary: { | |
| openDeals: openDeals.length, | |
| totalOpenValue, | |
| totalWonValue, | |
| winRate, | |
| activitiesThisWeek, | |
| }, | |
| dealsByStage, | |
| forecast, | |
| wonLost, | |
| recentActivities, | |
| }); | |
| } | |