import { Elysia } from 'elysia'; import { db } from '../db'; import { clients, events } from '../db/schema'; import { eq, and, sql, lte, gte, isNull, or } from 'drizzle-orm'; import type { User } from '../lib/auth'; export const insightsRoutes = new Elysia({ prefix: '/insights' }) .get('/', async ({ user }: { user: User }) => { const now = new Date(); const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000); const sevenDaysFromNow = new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000); // 1. Clients not contacted in 30+ days (or never contacted) const staleClients = await db.select({ id: clients.id, firstName: clients.firstName, lastName: clients.lastName, email: clients.email, company: clients.company, lastContactedAt: clients.lastContactedAt, }) .from(clients) .where(and( eq(clients.userId, user.id), or( isNull(clients.lastContactedAt), lte(clients.lastContactedAt, thirtyDaysAgo), ), )) .orderBy(clients.lastContactedAt) .limit(10); // 2. Upcoming birthdays this week // We need to compare month/day regardless of year const allClients = await db.select({ id: clients.id, firstName: clients.firstName, lastName: clients.lastName, birthday: clients.birthday, email: clients.email, }) .from(clients) .where(and( eq(clients.userId, user.id), sql`${clients.birthday} IS NOT NULL`, )); const upcomingBirthdays = allClients.filter(client => { if (!client.birthday) return false; const bday = new Date(client.birthday); // Set birthday to this year const thisYearBday = new Date(now.getFullYear(), bday.getMonth(), bday.getDate()); // Check if within next 7 days const diff = thisYearBday.getTime() - now.getTime(); return diff >= -24 * 60 * 60 * 1000 && diff <= 7 * 24 * 60 * 60 * 1000; // include today }).map(c => ({ id: c.id, firstName: c.firstName, lastName: c.lastName, birthday: c.birthday!.toISOString(), daysUntil: (() => { const bday = new Date(c.birthday!); const thisYearBday = new Date(now.getFullYear(), bday.getMonth(), bday.getDate()); return Math.ceil((thisYearBday.getTime() - now.getTime()) / (24 * 60 * 60 * 1000)); })(), })); // 3. Suggested follow-ups: clients contacted 14-30 days ago (proactive outreach window) const fourteenDaysAgo = new Date(now.getTime() - 14 * 24 * 60 * 60 * 1000); const suggestedFollowups = await db.select({ id: clients.id, firstName: clients.firstName, lastName: clients.lastName, email: clients.email, company: clients.company, lastContactedAt: clients.lastContactedAt, }) .from(clients) .where(and( eq(clients.userId, user.id), lte(clients.lastContactedAt, fourteenDaysAgo), gte(clients.lastContactedAt, thirtyDaysAgo), )) .orderBy(clients.lastContactedAt) .limit(5); // 4. Upcoming events (next 7 days) const upcomingEvents = await db.select({ id: events.id, title: events.title, type: events.type, date: events.date, clientId: events.clientId, }) .from(events) .where(and( eq(events.userId, user.id), gte(events.date, now), lte(events.date, sevenDaysFromNow), )) .orderBy(events.date) .limit(10); // 5. Summary stats const totalClients = await db.select({ count: sql`count(*)::int` }) .from(clients) .where(eq(clients.userId, user.id)); const neverContactedCount = await db.select({ count: sql`count(*)::int` }) .from(clients) .where(and(eq(clients.userId, user.id), isNull(clients.lastContactedAt))); return { staleClients: staleClients.map(c => ({ ...c, lastContactedAt: c.lastContactedAt?.toISOString() || null, daysSinceContact: c.lastContactedAt ? Math.floor((now.getTime() - c.lastContactedAt.getTime()) / (24 * 60 * 60 * 1000)) : null, })), upcomingBirthdays, suggestedFollowups: suggestedFollowups.map(c => ({ ...c, lastContactedAt: c.lastContactedAt?.toISOString() || null, daysSinceContact: c.lastContactedAt ? Math.floor((now.getTime() - c.lastContactedAt.getTime()) / (24 * 60 * 60 * 1000)) : null, })), upcomingEvents: upcomingEvents.map(e => ({ ...e, date: e.date.toISOString(), })), summary: { totalClients: totalClients[0]?.count || 0, neverContacted: neverContactedCount[0]?.count || 0, staleCount: staleClients.length, birthdaysThisWeek: upcomingBirthdays.length, }, }; });