refactor: professional code quality audit

- 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
This commit is contained in:
Z User committed 2026-10-27 12:23:32 +00:00
1 parent 969202764a
commit fa8dd46571
175 files changed
+24741 -2

No files matched your search

+56
View File
@@ -0,0 +1,56 @@
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 })
}
}