Schema fixes
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m59s

This commit is contained in:
2026-01-20 12:08:06 -07:00
parent 55eb03165e
commit 815c08dd50
17 changed files with 1381 additions and 307 deletions

View File

@@ -5,8 +5,8 @@ import TagChart from '../../components/TagChart.vue';
import ClientChart from '../../components/ClientChart.vue';
import MemberChart from '../../components/MemberChart.vue';
import { db } from '../../db';
import { timeEntries, members, users, clients, tags, timeEntryTags, invoices } from '../../db/schema';
import { eq, and, gte, lte, sql, desc, inArray, exists } from 'drizzle-orm';
import { timeEntries, members, users, clients, tags, invoices } from '../../db/schema';
import { eq, and, gte, lte, sql, desc } from 'drizzle-orm';
import { formatDuration, formatTimeRange } from '../../lib/formatTime';
const user = Astro.locals.user;
@@ -103,64 +103,27 @@ if (selectedMemberId) {
}
if (selectedTagId) {
conditions.push(exists(
db.select()
.from(timeEntryTags)
.where(and(
eq(timeEntryTags.timeEntryId, timeEntries.id),
eq(timeEntryTags.tagId, selectedTagId)
))
));
conditions.push(eq(timeEntries.tagId, selectedTagId));
}
if (selectedClientId) {
conditions.push(eq(timeEntries.clientId, selectedClientId));
}
const entriesData = await db.select({
const entries = await db.select({
entry: timeEntries,
user: users,
client: clients,
tag: tags,
})
.from(timeEntries)
.innerJoin(users, eq(timeEntries.userId, users.id))
.innerJoin(clients, eq(timeEntries.clientId, clients.id))
.leftJoin(tags, eq(timeEntries.tagId, tags.id))
.where(and(...conditions))
.orderBy(desc(timeEntries.startTime))
.all();
// Fetch tags for these entries
const entryIds = entriesData.map(e => e.entry.id);
const tagsMap = new Map<string, typeof allTags>();
if (entryIds.length > 0) {
// Process in chunks if too many entries, but for now simple inArray
// Sqlite has limits on variables, but usually ~999. Assuming reasonable page size or volume.
// If entryIds is massive, this might fail, but for a dashboard report it's usually acceptable or needs pagination/limits.
// However, `inArray` can be empty, so we checked length.
const entryTagsData = await db.select({
timeEntryId: timeEntryTags.timeEntryId,
tag: tags
})
.from(timeEntryTags)
.innerJoin(tags, eq(timeEntryTags.tagId, tags.id))
.where(inArray(timeEntryTags.timeEntryId, entryIds))
.all();
for (const item of entryTagsData) {
if (!tagsMap.has(item.timeEntryId)) {
tagsMap.set(item.timeEntryId, []);
}
tagsMap.get(item.timeEntryId)!.push(item.tag);
}
}
const entries = entriesData.map(e => ({
...e,
tags: tagsMap.get(e.entry.id) || []
}));
const statsByMember = teamMembers.map(member => {
const memberEntries = entries.filter(e => e.user.id === member.id);
const totalTime = memberEntries.reduce((sum, e) => {
@@ -178,7 +141,7 @@ const statsByMember = teamMembers.map(member => {
}).sort((a, b) => b.totalTime - a.totalTime);
const statsByTag = allTags.map(tag => {
const tagEntries = entries.filter(e => e.tags.some(t => t.id === tag.id));
const tagEntries = entries.filter(e => e.tag?.id === tag.id);
const totalTime = tagEntries.reduce((sum, e) => {
if (e.entry.endTime) {
return sum + (e.entry.endTime.getTime() - e.entry.startTime.getTime());
@@ -811,7 +774,7 @@ function getTimeRangeLabel(range: string) {
<th>Date</th>
<th>Member</th>
<th>Client</th>
<th>Tags</th>
<th>Tag</th>
<th>Description</th>
<th>Duration</th>
</tr>
@@ -828,17 +791,16 @@ function getTimeRangeLabel(range: string) {
<td>{e.user.name}</td>
<td>{e.client.name}</td>
<td>
<div class="flex flex-wrap gap-1">
{e.tags.map(tag => (
<div class="badge badge-sm badge-outline flex items-center gap-1">
{tag.color && (
<span class="w-2 h-2 rounded-full" style={`background-color: ${tag.color}`}></span>
)}
<span>{tag.name}</span>
</div>
))}
{e.tags.length === 0 && <span class="opacity-50">-</span>}
</div>
{e.tag ? (
<div class="badge badge-sm badge-outline flex items-center gap-1">
{e.tag.color && (
<span class="w-2 h-2 rounded-full" style={`background-color: ${e.tag.color}`}></span>
)}
<span>{e.tag.name}</span>
</div>
) : (
<span class="opacity-50">-</span>
)}
</td>
<td>{e.entry.description || '-'}</td>
<td class="font-mono">