- Fix all TypeScript compilation errors (0 errors in src/) - Add try/catch error handling to ALL API routes (38 handlers) - Fix empty catch blocks (api-auth.ts now logs warnings) - Add aria-labels to all icon-only buttons (WCAG 2.1 AA) - Fix Prisma type mismatches (OR filter, Person.companyId, Estimate.projectId, Ticket.createdAt) - Fix webhook PATCH route (moved to [id]/route.ts) - Fix mobile-bottom-nav type safety - Fix KB route where-clause type - Fix test-mail banner null vs undefined - Fix task comment fetch: add cancellation + error toast - ESLint: 0 errors, 0 warnings - 175 files, 24.741 lines of code
57 lines
2.4 KiB
TypeScript
Executable File
57 lines
2.4 KiB
TypeScript
Executable File
import { NextResponse } from 'next/server'
|
|
import { db } from '@/lib/db'
|
|
|
|
export const dynamic = 'force-dynamic'
|
|
|
|
// GET /api/aos/tickets — ticket inbox with SLA status
|
|
export async function GET() {
|
|
try {
|
|
const tickets = await db.ticket.findMany({
|
|
orderBy: [{ status: 'asc' }, { updatedAt: 'desc' }],
|
|
include: { messages: { orderBy: { createdAt: 'asc' } } },
|
|
})
|
|
|
|
const companyIds = [...new Set(tickets.map((t) => t.companyId))]
|
|
const companies = await db.company.findMany({ where: { id: { in: companyIds } }, select: { id: true, name: true } })
|
|
const companyMap = new Map(companies.map((c) => [c.id, c.name]))
|
|
|
|
const enriched = tickets.map((t) => {
|
|
const lastMsg = t.messages[t.messages.length - 1]
|
|
const ageHours = (Date.now() - t.createdAt.getTime()) / 3600000
|
|
const slaBreached = !t.slaRespondedAt && ageHours > t.slaResponseHours
|
|
const resolveBreached = !t.slaResolvedAt && ageHours > t.slaResolveHours
|
|
return {
|
|
...t,
|
|
companyName: companyMap.get(t.companyId) ?? '—',
|
|
lastMessage: lastMsg ? { fromName: lastMsg.fromName, fromType: lastMsg.fromType, body: lastMsg.body.slice(0, 120), createdAt: lastMsg.createdAt } : null,
|
|
messageCount: t.messages.length,
|
|
sla: {
|
|
responseHours: t.slaResponseHours,
|
|
resolveHours: t.slaResolveHours,
|
|
respondedAt: t.slaRespondedAt,
|
|
resolvedAt: t.slaResolvedAt,
|
|
responseBreached: slaBreached,
|
|
resolveBreached: resolveBreached,
|
|
ageHours: Math.round(ageHours * 10) / 10,
|
|
},
|
|
}
|
|
})
|
|
|
|
const stats = {
|
|
total: tickets.length,
|
|
open: tickets.filter((t) => t.status === 'open').length,
|
|
pending: tickets.filter((t) => t.status === 'pending').length,
|
|
resolved: tickets.filter((t) => t.status === 'resolved').length,
|
|
closed: tickets.filter((t) => t.status === 'closed').length,
|
|
urgent: tickets.filter((t) => t.priority === 'urgent' && t.status !== 'closed' && t.status !== 'resolved').length,
|
|
slaBreached: enriched.filter((t) => t.sla.responseBreached || t.sla.resolveBreached).length,
|
|
unresponded: tickets.filter((t) => !t.slaRespondedAt && t.status === 'open').length,
|
|
}
|
|
|
|
return NextResponse.json({ tickets: enriched, stats })
|
|
} catch (err) {
|
|
const msg = err instanceof Error ? err.message : 'internal server error'
|
|
return NextResponse.json({ error: msg }, { status: 500 })
|
|
}
|
|
}
|