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:
1 parent
969202764a
commit
fa8dd46571
175 files changed
+24741
-2
No files matched your search
Executable
+100
@@ -0,0 +1,100 @@
|
||||
import { NextResponse } from 'next/server'
|
||||
import { db } from '@/lib/db'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
// GET /api/aos/dashboard — agency KPI overview
|
||||
export async function GET() {
|
||||
try {
|
||||
const [deals, leads, projects, licenses, assets, invoices, audit, modules, users] = await Promise.all([
|
||||
db.deal.findMany({ include: { company: true } }),
|
||||
db.lead.findMany(),
|
||||
db.project.findMany(),
|
||||
db.license.findMany({ include: { activations: true, company: true } }),
|
||||
db.asset.findMany(),
|
||||
db.invoice.findMany(),
|
||||
db.auditLog.findMany({ orderBy: { createdAt: 'desc' }, take: 8 }),
|
||||
db.module.findMany(),
|
||||
db.user.findMany(),
|
||||
])
|
||||
|
||||
const pipelineValue = deals
|
||||
.filter((d) => d.stage !== 'lost' && d.stage !== 'won')
|
||||
.reduce((s, d) => s + d.valueCents, 0)
|
||||
|
||||
const wonValue = deals
|
||||
.filter((d) => d.stage === 'won')
|
||||
.reduce((s, d) => s + d.valueCents, 0)
|
||||
|
||||
const pendingLeads = leads.filter((l) => l.status === 'pending').length
|
||||
const approvedLeads = leads.filter((l) => l.status === 'approved').length
|
||||
const rejectedLeads = leads.filter((l) => l.status === 'rejected').length
|
||||
const acceptRate = approvedLeads + rejectedLeads > 0
|
||||
? Math.round((approvedLeads / (approvedLeads + rejectedLeads)) * 100)
|
||||
: 0
|
||||
|
||||
const openProjects = projects.filter((p) => p.status === 'active' || p.status === 'planning' || p.status === 'review').length
|
||||
|
||||
const activeLicenses = licenses.filter((l) => l.status === 'active').length
|
||||
const expiringAssets = assets.filter((a) => a.status === 'expiring' || a.status === 'expired').length
|
||||
|
||||
const outstandingInvoices = invoices
|
||||
.filter((i) => i.status === 'sent' || i.status === 'overdue')
|
||||
.reduce((s, i) => s + i.grossCents, 0)
|
||||
|
||||
const paidThisYear = invoices
|
||||
.filter((i) => i.status === 'paid')
|
||||
.reduce((s, i) => s + i.grossCents, 0)
|
||||
|
||||
const stages = ['lead', 'qualified', 'proposal', 'negotiation', 'won', 'lost']
|
||||
const stageLabels: Record<string, string> = {
|
||||
lead: 'Lead', qualified: 'Qualifiziert', proposal: 'Angebot', negotiation: 'Verhandlung', won: 'Gewonnen', lost: 'Verloren',
|
||||
}
|
||||
const funnel = stages.map((s) => ({
|
||||
stage: s,
|
||||
label: stageLabels[s],
|
||||
count: deals.filter((d) => d.stage === s).length,
|
||||
valueCents: deals.filter((d) => d.stage === s).reduce((sum, d) => sum + d.valueCents, 0),
|
||||
}))
|
||||
|
||||
const now = new Date()
|
||||
const months: { label: string; cents: number }[] = []
|
||||
for (let i = 5; i >= 0; i--) {
|
||||
const m = new Date(now.getFullYear(), now.getMonth() - i, 1)
|
||||
const label = m.toLocaleDateString('de-DE', { month: 'short' })
|
||||
const cents = invoices
|
||||
.filter((inv) => {
|
||||
const d = inv.issueDate
|
||||
return d.getMonth() === m.getMonth() && d.getFullYear() === m.getFullYear() && inv.status === 'paid'
|
||||
})
|
||||
.reduce((s, i) => s + i.grossCents, 0)
|
||||
months.push({ label, cents })
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
kpis: {
|
||||
pipelineValue,
|
||||
wonValue,
|
||||
pendingLeads,
|
||||
acceptRate,
|
||||
openProjects,
|
||||
activeLicenses,
|
||||
expiringAssets,
|
||||
outstandingInvoices,
|
||||
paidThisYear,
|
||||
auditEntries: audit.length,
|
||||
enabledModules: modules.filter((m) => m.enabled).length,
|
||||
totalModules: modules.length,
|
||||
teamMembers: users.filter((u) => u.role !== 'client').length,
|
||||
},
|
||||
funnel,
|
||||
revenue: months,
|
||||
audit,
|
||||
modules,
|
||||
users,
|
||||
})
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : 'internal server error'
|
||||
return NextResponse.json({ error: msg }, { status: 500 })
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user