"use client" import { useEffect, useState, useMemo } from "react" import { useRouter } from "next/navigation" import Image from "next/image" import Link from "next/link" import { Button } from "@/components/ui/button" import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog" import { KanbanBoard } from "@/components/dashboard/kanban-board" import { SearchFilter } from "@/components/dashboard/search-filter" import { formatPrice } from "@/lib/calculations" import { type StoredQuote, type QuoteStatus } from "@/lib/db" import { LogOut, List, Loader2, RotateCcw, ExternalLink } from "lucide-react" export default function DashboardPage() { const router = useRouter() const [quotes, setQuotes] = useState([]) const [loading, setLoading] = useState(true) const [search, setSearch] = useState("") // Dialogs const [rejectQuote, setRejectQuote] = useState(null) const [showRejected, setShowRejected] = useState(false) useEffect(() => { fetchQuotes() }, []) async function fetchQuotes() { try { const res = await fetch("/api/quotes") if (!res.ok) { if (res.status === 401) { router.push("/login") return } throw new Error("Failed to fetch quotes") } const data = await res.json() setQuotes(data.quotes) } catch (error) { console.error("Failed to fetch quotes:", error) } finally { setLoading(false) } } async function handleStatusChange(id: number, status: QuoteStatus) { // Optimistic update setQuotes((prev) => prev.map((q) => (q.id === id ? { ...q, status } : q))) try { const res = await fetch("/api/quotes", { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ id, status }), }) if (!res.ok) { fetchQuotes() } } catch { fetchQuotes() } } function handleRejectClick(quote: StoredQuote) { setRejectQuote(quote) } async function confirmReject() { if (!rejectQuote) return await handleStatusChange(rejectQuote.id, "rejected") setRejectQuote(null) } async function handleLogout() { try { await fetch("/api/auth/logout", { method: "POST" }) router.push("/login") router.refresh() } catch (error) { console.error("Logout failed:", error) } } const filteredQuotes = useMemo(() => { if (!search.trim()) return quotes const term = search.toLowerCase() return quotes.filter( (q) => q.customerName.toLowerCase().includes(term) || q.customerEmail.toLowerCase().includes(term) || q.postalCode.includes(term) ) }, [quotes, search]) const activeQuotes = filteredQuotes.filter((q) => q.status !== "rejected") const rejectedQuotes = filteredQuotes.filter((q) => q.status === "rejected") if (loading) { return (
) } return (
{/* Header */}
Foam King

Dashboard

{/* Main content */}
{/* Search */}
{/* Kanban board */} setShowRejected(true)} /> {/* Empty state */} {quotes.length === 0 && !loading && (

Ingen tilbud endnu. Når kunder anmoder om tilbud, vil de vises her.

)}
{/* Reject confirmation dialog */} setRejectQuote(null)}> Afvis tilbud? Er du sikker på du vil afvise tilbuddet til{" "} {rejectQuote?.customerName}? {rejectQuote && (
Tilbud #{rejectQuote.id} {rejectQuote.totalInclVat ? formatPrice(Math.round(rejectQuote.totalInclVat)) : "—"}
{rejectQuote.postalCode} · {rejectQuote.area} m²
)}
{/* Rejected quotes modal */} Afviste tilbud ({rejectedQuotes.length})
{rejectedQuotes.length === 0 ? (

Ingen afviste tilbud

) : ( rejectedQuotes.map((quote) => { const slug = `${quote.postalCode}-${quote.id}` return (
{quote.customerName}
{quote.postalCode} · {quote.area} m² ·{" "} {quote.totalInclVat ? formatPrice(Math.round(quote.totalInclVat)) : "—"}
) }) )}
) }