Kanban board, Project Management, Proposals/Quotes, Time Tracking, Custom Fields
Build & Auto-Repair / build (push) Has been cancelled

- Kanban board with drag-and-drop per stage, colour-coded cards, score bars
- Project Management with milestones, Gantt timeline, inline add milestone/task
- Proposal/Quote builder with line items, VAT, terms, e-signature capture
- Time Tracking with stopwatch, manual entry, hours-by-project chart
- Custom Fields system: admin-defined fields per entity (leads/customers/
  projects/opportunities) with drag-and-drop reordering in settings
- Reusable CustomFieldsSection component for entity detail pages
- Customers API endpoint
- All builds pass with zero errors
This commit is contained in:
2026-07-01 11:18:59 +02:00
parent 5d971dc749
commit 545065b527
38 changed files with 3770 additions and 4 deletions
+73
View File
@@ -0,0 +1,73 @@
import { NextRequest, NextResponse } from "next/server"
import { getSessionUser } from "@/lib/auth"
import { query } from "@/lib/db"
function stageStatus(name: string): string {
switch (name) {
case "New": return "open"
case "Contacted": return "contacted"
case "Qualified":
case "Interested":
case "Demo Scheduled":
case "Negotiation": return "pending"
case "Closed Won": return "closed"
case "Closed Lost": return "ignored"
default: return "open"
}
}
export async function GET(request: NextRequest) {
try {
const user = await getSessionUser()
if (!user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
const isAdmin = user.role === "admin" || user.role === "super_admin"
const result = await query(
`SELECT ls.id, ls.name, ls.sort_order, ls.probability,
COALESCE(
json_agg(
json_build_object(
'id', l.id,
'company_name', l.company_name,
'contact_name', l.contact_name,
'email', l.email,
'phone', l.phone,
'score', l.score,
'notes', l.notes,
'assigned_to', l.assigned_to,
'created_at', l.created_at
)
ORDER BY l.created_at DESC
) FILTER (WHERE l.id IS NOT NULL),
'[]'::json
) AS leads
FROM lead_stages ls
LEFT JOIN leads l ON l.stage_id = ls.id AND l.deleted_at IS NULL
WHERE ls.is_active = true
GROUP BY ls.id, ls.name, ls.sort_order, ls.probability
ORDER BY ls.sort_order`
)
const columns = result.rows.map((r: any) => {
const status = stageStatus(r.name)
let leads = r.leads || []
if (!isAdmin) {
leads = leads.filter((l: any) => l.assigned_to === user.id)
}
return {
id: r.id,
name: r.name,
status,
sortOrder: r.sort_order,
probability: r.probability,
leads,
}
})
return NextResponse.json(columns)
} catch (error) {
console.error("Kanban API error:", error)
return NextResponse.json({ error: "Failed to load kanban data" }, { status: 500 })
}
}