import type { APIRoute } from 'astro'; import { db } from '../../../db'; import { timeEntries, members, users, clients, categories } from '../../../db/schema'; import { eq, and, gte, lte, desc } from 'drizzle-orm'; export const GET: APIRoute = async ({ request, locals, cookies }) => { const user = locals.user; if (!user) { return new Response('Unauthorized', { status: 401 }); } // Get current team from cookie const currentTeamId = cookies.get('currentTeamId')?.value; const userMemberships = await db.select() .from(members) .where(eq(members.userId, user.id)) .all(); if (userMemberships.length === 0) { return new Response('No organization found', { status: 404 }); } // Use current team or fallback to first membership const userMembership = currentTeamId ? userMemberships.find(m => m.organizationId === currentTeamId) || userMemberships[0] : userMemberships[0]; const url = new URL(request.url); const selectedMemberId = url.searchParams.get('member') || ''; const selectedCategoryId = url.searchParams.get('category') || ''; const selectedClientId = url.searchParams.get('client') || ''; const timeRange = url.searchParams.get('range') || 'week'; const customFrom = url.searchParams.get('from'); const customTo = url.searchParams.get('to'); const now = new Date(); let startDate = new Date(); let endDate = new Date(); switch (timeRange) { case 'today': startDate.setHours(0, 0, 0, 0); endDate.setHours(23, 59, 59, 999); break; case 'week': startDate.setDate(now.getDate() - 7); break; case 'month': startDate.setMonth(now.getMonth() - 1); break; case 'mtd': startDate = new Date(now.getFullYear(), now.getMonth(), 1); break; case 'ytd': startDate = new Date(now.getFullYear(), 0, 1); break; case 'last-month': startDate = new Date(now.getFullYear(), now.getMonth() - 1, 1); endDate = new Date(now.getFullYear(), now.getMonth(), 0, 23, 59, 59, 999); break; case 'custom': if (customFrom) { const parts = customFrom.split('-'); startDate = new Date(parseInt(parts[0]), parseInt(parts[1]) - 1, parseInt(parts[2]), 0, 0, 0, 0); } if (customTo) { const parts = customTo.split('-'); endDate = new Date(parseInt(parts[0]), parseInt(parts[1]) - 1, parseInt(parts[2]), 23, 59, 59, 999); } break; } const conditions = [ eq(timeEntries.organizationId, userMembership.organizationId), gte(timeEntries.startTime, startDate), lte(timeEntries.startTime, endDate), ]; if (selectedMemberId) { conditions.push(eq(timeEntries.userId, selectedMemberId)); } if (selectedCategoryId) { conditions.push(eq(timeEntries.categoryId, selectedCategoryId)); } if (selectedClientId) { conditions.push(eq(timeEntries.clientId, selectedClientId)); } const entries = await db.select({ entry: timeEntries, user: users, client: clients, category: categories, }) .from(timeEntries) .innerJoin(users, eq(timeEntries.userId, users.id)) .innerJoin(clients, eq(timeEntries.clientId, clients.id)) .innerJoin(categories, eq(timeEntries.categoryId, categories.id)) .where(and(...conditions)) .orderBy(desc(timeEntries.startTime)) .all(); // Generate CSV const headers = ['Date', 'Start Time', 'End Time', 'Duration (h)', 'Member', 'Client', 'Category', 'Description']; const rows = entries.map(e => { const start = e.entry.startTime; const end = e.entry.endTime; let duration = 0; if (end) { duration = (end.getTime() - start.getTime()) / (1000 * 60 * 60); // Hours } return [ start.toLocaleDateString(), start.toLocaleTimeString(), end ? end.toLocaleTimeString() : '', end ? duration.toFixed(2) : 'Running', `"${(e.user.name || '').replace(/"/g, '""')}"`, `"${(e.client.name || '').replace(/"/g, '""')}"`, `"${(e.category.name || '').replace(/"/g, '""')}"`, `"${(e.entry.description || '').replace(/"/g, '""')}"` ].join(','); }); const csvContent = [headers.join(','), ...rows].join('\n'); return new Response(csvContent, { headers: { 'Content-Type': 'text/csv', 'Content-Disposition': `attachment; filename="time-entries-${startDate.toISOString().split('T')[0]}-to-${endDate.toISOString().split('T')[0]}.csv"`, }, }); };