--- import DashboardLayout from '../../layouts/DashboardLayout.astro'; import { Icon } from 'astro-icon/components'; import Timer from '../../components/Timer.vue'; import ManualEntry from '../../components/ManualEntry.vue'; import { db } from '../../db'; import { timeEntries, clients, tags, users } from '../../db/schema'; import { eq, desc, asc, and, sql, or, like } from 'drizzle-orm'; import { formatTimeRange } from '../../lib/formatTime'; import { getCurrentTeam } from '../../lib/getCurrentTeam'; const user = Astro.locals.user; if (!user) return Astro.redirect('/login'); const userMembership = await getCurrentTeam(user, Astro.cookies.get('currentTeamId')?.value); if (!userMembership) return Astro.redirect('/dashboard'); const organizationId = userMembership.organizationId; const allClients = await db.select() .from(clients) .where(eq(clients.organizationId, organizationId)) .all(); const allTags = await db.select() .from(tags) .where(eq(tags.organizationId, organizationId)) .all(); // Query params const url = new URL(Astro.request.url); const page = parseInt(url.searchParams.get('page') || '1'); const pageSize = 20; const offset = (page - 1) * pageSize; const filterClient = url.searchParams.get('client') || ''; const filterStatus = url.searchParams.get('status') || ''; const filterType = url.searchParams.get('type') || ''; const sortBy = url.searchParams.get('sort') || 'start-desc'; const searchTerm = url.searchParams.get('search') || ''; const conditions = [eq(timeEntries.organizationId, organizationId)]; if (filterClient) { conditions.push(eq(timeEntries.clientId, filterClient)); } if (filterStatus === 'completed') { conditions.push(sql`${timeEntries.endTime} IS NOT NULL`); } else if (filterStatus === 'running') { conditions.push(sql`${timeEntries.endTime} IS NULL`); } if (searchTerm) { conditions.push(like(timeEntries.description, `%${searchTerm}%`)); } if (filterType === 'manual') { conditions.push(eq(timeEntries.isManual, true)); } else if (filterType === 'timed') { conditions.push(eq(timeEntries.isManual, false)); } const totalCount = await db.select({ count: sql`count(*)` }) .from(timeEntries) .where(and(...conditions)) .get(); const totalPages = Math.ceil((totalCount?.count || 0) / pageSize); let orderBy; switch (sortBy) { case 'start-asc': orderBy = asc(timeEntries.startTime); break; case 'duration-desc': orderBy = desc(sql`(CASE WHEN ${timeEntries.endTime} IS NULL THEN 0 ELSE ${timeEntries.endTime} - ${timeEntries.startTime} END)`); break; case 'duration-asc': orderBy = asc(sql`(CASE WHEN ${timeEntries.endTime} IS NULL THEN 0 ELSE ${timeEntries.endTime} - ${timeEntries.startTime} END)`); break; default: orderBy = desc(timeEntries.startTime); } const entries = await db.select({ entry: timeEntries, client: clients, user: users, tag: tags, }) .from(timeEntries) .leftJoin(clients, eq(timeEntries.clientId, clients.id)) .leftJoin(users, eq(timeEntries.userId, users.id)) .leftJoin(tags, eq(timeEntries.tagId, tags.id)) .where(and(...conditions)) .orderBy(orderBy) .limit(pageSize) .offset(offset) .all(); const runningEntry = await db.select({ entry: timeEntries, client: clients, tag: tags, }) .from(timeEntries) .leftJoin(clients, eq(timeEntries.clientId, clients.id)) .leftJoin(tags, eq(timeEntries.tagId, tags.id)) .where(and( eq(timeEntries.userId, user.id), sql`${timeEntries.endTime} IS NULL` )) .get(); function getPaginationPages(currentPage: number, totalPages: number): number[] { const pages: number[] = []; const numPagesToShow = Math.min(5, totalPages); for (let i = 0; i < numPagesToShow; i++) { let pageNum; if (totalPages <= 5) { pageNum = i + 1; } else if (currentPage <= 3) { pageNum = i + 1; } else if (currentPage >= totalPages - 2) { pageNum = totalPages - 4 + i; } else { pageNum = currentPage - 2 + i; } pages.push(pageNum); } return pages; } const paginationPages = getPaginationPages(page, totalPages); ---

Time Tracker

{allClients.length === 0 ? (
You need to create a client before tracking time. Add Client
) : ( ({ id: c.id, name: c.name }))} tags={allTags.map(t => ({ id: t.id, name: t.name, color: t.color }))} /> )}
{allClients.length === 0 ? (
You need to create a client before adding time entries. Add Client
) : ( ({ id: c.id, name: c.name }))} tags={allTags.map(t => ({ id: t.id, name: t.name, color: t.color }))} /> )}
{allClients.length === 0 ? ( ) : null}

Time Entries ({totalCount?.count || 0} total)

{(filterClient || filterStatus || filterType || searchTerm) && ( Clear Filters )}
{entries.map(({ entry, client, user: entryUser }) => ( ))}
Type Client Description Member Start Time End Time Duration Actions
{entry.isManual ? ( Manual ) : ( Timed )} {client?.name || 'Unknown'} {entry.description || '-'} {entryUser?.name || 'Unknown'} {entry.startTime.toLocaleDateString()}
{entry.startTime.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})}
{entry.endTime ? ( <> {entry.endTime.toLocaleDateString()}
{entry.endTime.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})} ) : ( Running )}
{formatTimeRange(entry.startTime, entry.endTime)}
{totalPages > 1 && (
Previous
{paginationPages.map(pageNum => ( {pageNum} ))}
Next
)}