This commit is contained in:
@@ -1,72 +1,96 @@
|
||||
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';
|
||||
import type { APIRoute } from "astro";
|
||||
import { db } from "../../../db";
|
||||
import {
|
||||
timeEntries,
|
||||
members,
|
||||
users,
|
||||
clients,
|
||||
tags,
|
||||
timeEntryTags,
|
||||
} from "../../../db/schema";
|
||||
import { eq, and, gte, lte, desc, inArray } from "drizzle-orm";
|
||||
|
||||
export const GET: APIRoute = async ({ request, locals, cookies }) => {
|
||||
const user = locals.user;
|
||||
if (!user) {
|
||||
return new Response('Unauthorized', { status: 401 });
|
||||
return new Response("Unauthorized", { status: 401 });
|
||||
}
|
||||
|
||||
// Get current team from cookie
|
||||
const currentTeamId = cookies.get('currentTeamId')?.value;
|
||||
const currentTeamId = cookies.get("currentTeamId")?.value;
|
||||
|
||||
const userMemberships = await db.select()
|
||||
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 });
|
||||
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.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 selectedMemberId = url.searchParams.get("member") || "";
|
||||
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':
|
||||
case "today":
|
||||
startDate.setHours(0, 0, 0, 0);
|
||||
endDate.setHours(23, 59, 59, 999);
|
||||
break;
|
||||
case 'week':
|
||||
case "week":
|
||||
startDate.setDate(now.getDate() - 7);
|
||||
break;
|
||||
case 'month':
|
||||
case "month":
|
||||
startDate.setMonth(now.getMonth() - 1);
|
||||
break;
|
||||
case 'mtd':
|
||||
case "mtd":
|
||||
startDate = new Date(now.getFullYear(), now.getMonth(), 1);
|
||||
break;
|
||||
case 'ytd':
|
||||
case "ytd":
|
||||
startDate = new Date(now.getFullYear(), 0, 1);
|
||||
break;
|
||||
case 'last-month':
|
||||
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':
|
||||
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);
|
||||
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);
|
||||
const parts = customTo.split("-");
|
||||
endDate = new Date(
|
||||
parseInt(parts[0]),
|
||||
parseInt(parts[1]) - 1,
|
||||
parseInt(parts[2]),
|
||||
23,
|
||||
59,
|
||||
59,
|
||||
999,
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -81,31 +105,58 @@ export const GET: APIRoute = async ({ request, locals, cookies }) => {
|
||||
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,
|
||||
})
|
||||
const entries = await db
|
||||
.select({
|
||||
entry: timeEntries,
|
||||
user: users,
|
||||
client: clients,
|
||||
})
|
||||
.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();
|
||||
|
||||
// Fetch tags for these entries
|
||||
const entryIds = entries.map((e) => e.entry.id);
|
||||
const tagsMap = new Map<string, string[]>();
|
||||
|
||||
if (entryIds.length > 0) {
|
||||
const entryTags = await db
|
||||
.select({
|
||||
entryId: timeEntryTags.timeEntryId,
|
||||
tagName: tags.name,
|
||||
})
|
||||
.from(timeEntryTags)
|
||||
.innerJoin(tags, eq(timeEntryTags.tagId, tags.id))
|
||||
.where(inArray(timeEntryTags.timeEntryId, entryIds))
|
||||
.all();
|
||||
|
||||
for (const tag of entryTags) {
|
||||
if (!tagsMap.has(tag.entryId)) {
|
||||
tagsMap.set(tag.entryId, []);
|
||||
}
|
||||
tagsMap.get(tag.entryId)!.push(tag.tagName);
|
||||
}
|
||||
}
|
||||
|
||||
// Generate CSV
|
||||
const headers = ['Date', 'Start Time', 'End Time', 'Duration (h)', 'Member', 'Client', 'Category', 'Description'];
|
||||
const rows = entries.map(e => {
|
||||
const headers = [
|
||||
"Date",
|
||||
"Start Time",
|
||||
"End Time",
|
||||
"Duration (h)",
|
||||
"Member",
|
||||
"Client",
|
||||
"Tags",
|
||||
"Description",
|
||||
];
|
||||
const rows = entries.map((e) => {
|
||||
const start = e.entry.startTime;
|
||||
const end = e.entry.endTime;
|
||||
|
||||
@@ -114,24 +165,26 @@ export const GET: APIRoute = async ({ request, locals, cookies }) => {
|
||||
duration = (end.getTime() - start.getTime()) / (1000 * 60 * 60); // Hours
|
||||
}
|
||||
|
||||
const tagsStr = tagsMap.get(e.entry.id)?.join("; ") || "";
|
||||
|
||||
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(',');
|
||||
end ? end.toLocaleTimeString() : "",
|
||||
end ? duration.toFixed(2) : "Running",
|
||||
`"${(e.user.name || "").replace(/"/g, '""')}"`,
|
||||
`"${(e.client.name || "").replace(/"/g, '""')}"`,
|
||||
`"${tagsStr.replace(/"/g, '""')}"`,
|
||||
`"${(e.entry.description || "").replace(/"/g, '""')}"`,
|
||||
].join(",");
|
||||
});
|
||||
|
||||
const csvContent = [headers.join(','), ...rows].join('\n');
|
||||
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"`,
|
||||
"Content-Type": "text/csv",
|
||||
"Content-Disposition": `attachment; filename="time-entries-${startDate.toISOString().split("T")[0]}-to-${endDate.toISOString().split("T")[0]}.csv"`,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user