foamking/components/dashboard/kanban-board.tsx
mikl0s 3ebb63dc6c Add admin dashboard, authentication, step wizard, and quote management
Expand the calculator with a multi-step wizard flow, admin dashboard with
quote tracking, login/auth system, distance API integration, and history
page. Add new UI components (dialog, progress, select, slider, switch),
update pricing logic, and improve the overall design with new assets.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 20:59:11 +00:00

134 lines
3.7 KiB
TypeScript

"use client"
import { formatPrice } from "@/lib/calculations"
import { type StoredQuote, type QuoteStatus } from "@/lib/db"
import { QuoteCard } from "./quote-card"
interface KanbanColumnProps {
title: string
status: QuoteStatus
quotes: StoredQuote[]
showTotal?: boolean
onStatusChange: (id: number, status: QuoteStatus) => void
onReject: (quote: StoredQuote) => void
}
function KanbanColumn({
title,
status,
quotes,
showTotal = true,
onStatusChange,
onReject,
}: KanbanColumnProps) {
const total = quotes.reduce((sum, q) => sum + (q.totalInclVat || 0), 0)
const bgColor = {
new: "bg-blue-50/50 border-blue-200",
contacted: "bg-amber-50/50 border-amber-200",
accepted: "bg-green-50/50 border-green-200",
rejected: "bg-gray-50/50 border-gray-200",
}[status]
const headerColor = {
new: "text-blue-700",
contacted: "text-amber-700",
accepted: "text-green-700",
rejected: "text-gray-500",
}[status]
return (
<div className={`flex flex-col rounded-xl border ${bgColor}`}>
{/* Fixed header */}
<div className="flex-shrink-0 border-b border-inherit p-3">
<div className="flex items-center justify-between">
<h2 className={`font-semibold ${headerColor}`}>{title}</h2>
<span className={`text-sm font-medium ${headerColor}`}>{quotes.length}</span>
</div>
{showTotal && quotes.length > 0 && (
<div className="mt-1 text-sm text-muted-foreground">
Værdi: <span className="font-medium">{formatPrice(Math.round(total))}</span>
</div>
)}
</div>
{/* Scrollable content */}
<div
className="flex-1 space-y-2 overflow-y-auto p-2"
style={{ maxHeight: "calc(100vh - 220px)" }}
>
{quotes.length === 0 ? (
<p className="py-6 text-center text-xs text-muted-foreground">Ingen tilbud</p>
) : (
quotes.map((quote) => (
<QuoteCard
key={quote.id}
quote={quote}
onStatusChange={onStatusChange}
onReject={onReject}
/>
))
)}
</div>
</div>
)
}
interface KanbanBoardProps {
quotes: StoredQuote[]
onStatusChange: (id: number, status: QuoteStatus) => void
onReject: (quote: StoredQuote) => void
rejectedCount: number
onShowRejected: () => void
}
export function KanbanBoard({
quotes,
onStatusChange,
onReject,
rejectedCount,
onShowRejected,
}: KanbanBoardProps) {
// Only show 3 main columns
const columns: { title: string; status: QuoteStatus; showTotal: boolean }[] = [
{ title: "Nye tilbud", status: "new", showTotal: true },
{ title: "Kunde kontaktet", status: "contacted", showTotal: true },
{ title: "Tilbud accepteret", status: "accepted", showTotal: true },
]
const groupedQuotes = columns.reduce(
(acc, col) => {
acc[col.status] = quotes.filter((q) => q.status === col.status)
return acc
},
{} as Record<QuoteStatus, StoredQuote[]>
)
return (
<div>
<div className="grid grid-cols-1 gap-4 md:grid-cols-3">
{columns.map((col) => (
<KanbanColumn
key={col.status}
title={col.title}
status={col.status}
quotes={groupedQuotes[col.status] || []}
showTotal={col.showTotal}
onStatusChange={onStatusChange}
onReject={onReject}
/>
))}
</div>
{/* Rejected quotes link */}
{rejectedCount > 0 && (
<button
onClick={onShowRejected}
className="mt-4 text-sm text-muted-foreground hover:text-foreground hover:underline"
>
Se {rejectedCount} afviste tilbud
</button>
)}
</div>
)
}