153 lines
4.8 KiB
TypeScript
153 lines
4.8 KiB
TypeScript
import { Elysia, t } from 'elysia';
|
|
import { db } from '../db';
|
|
import { clients, events, communications, interactions } from '../db/schema';
|
|
import { eq, and, desc } from 'drizzle-orm';
|
|
import type { User } from '../lib/auth';
|
|
|
|
export interface ActivityItem {
|
|
id: string;
|
|
type: 'email_sent' | 'email_drafted' | 'event_created' | 'client_contacted' | 'client_created' | 'client_updated' | 'interaction';
|
|
title: string;
|
|
description?: string;
|
|
date: string;
|
|
metadata?: Record<string, any>;
|
|
}
|
|
|
|
export const activityRoutes = new Elysia({ prefix: '/clients' })
|
|
// Get activity timeline for a client
|
|
.get('/:id/activity', async ({ params, user }: { params: { id: string }; user: User }) => {
|
|
// Verify client belongs to user
|
|
const [client] = await db.select()
|
|
.from(clients)
|
|
.where(and(eq(clients.id, params.id), eq(clients.userId, user.id)))
|
|
.limit(1);
|
|
|
|
if (!client) {
|
|
throw new Error('Client not found');
|
|
}
|
|
|
|
const activities: ActivityItem[] = [];
|
|
|
|
// Client creation
|
|
activities.push({
|
|
id: `created-${client.id}`,
|
|
type: 'client_created',
|
|
title: 'Client added to network',
|
|
date: client.createdAt.toISOString(),
|
|
});
|
|
|
|
// Client updated (if different from created)
|
|
if (client.updatedAt.getTime() - client.createdAt.getTime() > 60000) {
|
|
activities.push({
|
|
id: `updated-${client.id}`,
|
|
type: 'client_updated',
|
|
title: 'Client profile updated',
|
|
date: client.updatedAt.toISOString(),
|
|
});
|
|
}
|
|
|
|
// Last contacted
|
|
if (client.lastContactedAt) {
|
|
activities.push({
|
|
id: `contacted-${client.id}`,
|
|
type: 'client_contacted',
|
|
title: 'Marked as contacted',
|
|
date: client.lastContactedAt.toISOString(),
|
|
});
|
|
}
|
|
|
|
// Communications (emails)
|
|
const comms = await db.select()
|
|
.from(communications)
|
|
.where(and(
|
|
eq(communications.clientId, params.id),
|
|
eq(communications.userId, user.id),
|
|
))
|
|
.orderBy(desc(communications.createdAt));
|
|
|
|
for (const comm of comms) {
|
|
if (comm.status === 'sent' && comm.sentAt) {
|
|
activities.push({
|
|
id: `email-sent-${comm.id}`,
|
|
type: 'email_sent',
|
|
title: `Email sent: ${comm.subject || 'No subject'}`,
|
|
description: comm.content.substring(0, 150) + (comm.content.length > 150 ? '...' : ''),
|
|
date: comm.sentAt.toISOString(),
|
|
metadata: { emailId: comm.id, aiGenerated: comm.aiGenerated },
|
|
});
|
|
}
|
|
|
|
// Also show drafts
|
|
if (comm.status === 'draft') {
|
|
activities.push({
|
|
id: `email-draft-${comm.id}`,
|
|
type: 'email_drafted',
|
|
title: `Email drafted: ${comm.subject || 'No subject'}`,
|
|
description: comm.content.substring(0, 150) + (comm.content.length > 150 ? '...' : ''),
|
|
date: comm.createdAt.toISOString(),
|
|
metadata: { emailId: comm.id, aiGenerated: comm.aiGenerated },
|
|
});
|
|
}
|
|
}
|
|
|
|
// Events
|
|
const clientEvents = await db.select()
|
|
.from(events)
|
|
.where(and(
|
|
eq(events.clientId, params.id),
|
|
eq(events.userId, user.id),
|
|
))
|
|
.orderBy(desc(events.createdAt));
|
|
|
|
for (const event of clientEvents) {
|
|
activities.push({
|
|
id: `event-${event.id}`,
|
|
type: 'event_created',
|
|
title: `Event: ${event.title}`,
|
|
description: `${event.type}${event.recurring ? ' (recurring)' : ''}`,
|
|
date: event.createdAt.toISOString(),
|
|
metadata: { eventId: event.id, eventType: event.type, eventDate: event.date.toISOString() },
|
|
});
|
|
}
|
|
|
|
// Interactions
|
|
const clientInteractions = await db.select()
|
|
.from(interactions)
|
|
.where(and(
|
|
eq(interactions.clientId, params.id),
|
|
eq(interactions.userId, user.id),
|
|
))
|
|
.orderBy(desc(interactions.contactedAt));
|
|
|
|
for (const interaction of clientInteractions) {
|
|
const typeLabels: Record<string, string> = {
|
|
call: '📞 Phone Call',
|
|
meeting: '🤝 Meeting',
|
|
email: '✉️ Email',
|
|
note: '📝 Note',
|
|
other: '📌 Interaction',
|
|
};
|
|
activities.push({
|
|
id: `interaction-${interaction.id}`,
|
|
type: 'interaction',
|
|
title: `${typeLabels[interaction.type] || typeLabels.other}: ${interaction.title}`,
|
|
description: interaction.description || undefined,
|
|
date: interaction.contactedAt.toISOString(),
|
|
metadata: {
|
|
interactionId: interaction.id,
|
|
interactionType: interaction.type,
|
|
duration: interaction.duration,
|
|
},
|
|
});
|
|
}
|
|
|
|
// Sort by date descending
|
|
activities.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
|
|
|
|
return activities;
|
|
}, {
|
|
params: t.Object({
|
|
id: t.String({ format: 'uuid' }),
|
|
}),
|
|
});
|