feat: ticket modal collapsible notes and time entries

- New GET /api/tickets/[id]/notes: fetches TicketNotes from Autotask,
  enriches with creator resource names, sorted newest first
- Modal: collapsible Notes section (lazy fetch on expand, shows count badge)
- Modal: collapsible Time Entries section (lazy fetch, count + total hours in header)
- Both sections fetch once and cache for the modal session
This commit is contained in:
lorentz 2026-03-23 10:27:31 -04:00
parent 0a318fb9d6
commit b72fe2c70e
2 changed files with 215 additions and 9 deletions

View file

@ -0,0 +1,53 @@
import { NextRequest, NextResponse } from 'next/server';
import { getAutotaskClient } from '@/lib/services/autotask-factory';
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const { id } = await params;
const client = getAutotaskClient();
const notes = await client.queryEntity('TicketNotes', {
filter: [
{ op: 'eq', field: 'ticketID', value: parseInt(id) }
],
});
// Enrich with resource names
const resourceCache: Record<number, string> = {};
const enriched = await Promise.all(
(notes as any[]).map(async (note) => {
let creatorName = null;
const resourceId = note.creatorResourceID;
if (resourceId) {
if (resourceCache[resourceId]) {
creatorName = resourceCache[resourceId];
} else {
try {
const resource = await client.getEntityById('Resources', resourceId) as any;
creatorName = resource ? `${resource.firstName} ${resource.lastName}` : null;
if (creatorName) resourceCache[resourceId] = creatorName;
} catch {
// ignore
}
}
}
return { ...note, creatorName };
})
);
enriched.sort((a, b) =>
new Date(b.createDateTime || 0).getTime() - new Date(a.createDateTime || 0).getTime()
);
return NextResponse.json({ notes: enriched });
} catch (error) {
console.error('Error fetching ticket notes:', error);
return NextResponse.json(
{ error: 'Failed to fetch ticket notes' },
{ status: 500 }
);
}
}