204 lines
7.5 KiB
TypeScript
204 lines
7.5 KiB
TypeScript
"use client";
|
||
|
||
import { useEffect, useState } from "react";
|
||
import Link from "next/link";
|
||
import { useRouter } from "next/navigation";
|
||
import { LogOut, Package, Clock, CheckCircle2, XCircle, AlertCircle, UserCog } from "lucide-react";
|
||
import { Button } from "@/components/ui/button";
|
||
import { Separator } from "@/components/ui/separator";
|
||
import { supabase } from "@/lib/supabase";
|
||
import type { User } from "@supabase/supabase-js";
|
||
|
||
const STATUS_MAP: Record<string, { label: string; icon: React.ElementType; color: string }> = {
|
||
offen: { label: "Offen", icon: Clock, color: "text-amber-700 bg-amber-50 border-amber-200" },
|
||
bestaetigt: { label: "Bestätigt", icon: CheckCircle2, color: "text-green-700 bg-green-50 border-green-200" },
|
||
abgelehnt: { label: "Abgelehnt", icon: XCircle, color: "text-red-700 bg-red-50 border-red-200" },
|
||
abgeschlossen: { label: "Abgeschlossen", icon: CheckCircle2, color: "text-slate-600 bg-slate-50 border-slate-200" },
|
||
};
|
||
|
||
interface Anfrage {
|
||
id: string;
|
||
created_at: string;
|
||
status: string;
|
||
firma: string;
|
||
telefon: string;
|
||
email: string;
|
||
notizen: string;
|
||
anfragen_positionen: {
|
||
id: string;
|
||
maschine_name: string;
|
||
mietbeginn: string;
|
||
mietende: string;
|
||
gesamt_tage: number;
|
||
lieferung: boolean;
|
||
tagessatz: number | null;
|
||
}[];
|
||
}
|
||
|
||
function formatDatum(iso: string) {
|
||
return new Date(iso).toLocaleDateString("de-DE", {
|
||
day: "2-digit",
|
||
month: "2-digit",
|
||
year: "numeric",
|
||
});
|
||
}
|
||
|
||
export default function KundenDashboardPage() {
|
||
const router = useRouter();
|
||
const [user, setUser] = useState<User | null>(null);
|
||
const [anfragen, setAnfragen] = useState<Anfrage[]>([]);
|
||
const [loading, setLoading] = useState(true);
|
||
|
||
useEffect(() => {
|
||
async function init() {
|
||
const { data: { session } } = await supabase.auth.getSession();
|
||
if (!session) {
|
||
router.push("/kunden/login");
|
||
return;
|
||
}
|
||
setUser(session.user);
|
||
|
||
// Anfragen laden
|
||
const res = await fetch("/api/kunden/anfragen", {
|
||
headers: { Authorization: `Bearer ${session.access_token}` },
|
||
});
|
||
if (res.ok) {
|
||
const json = await res.json();
|
||
setAnfragen(json.anfragen ?? []);
|
||
}
|
||
setLoading(false);
|
||
}
|
||
init();
|
||
}, [router]);
|
||
|
||
async function handleLogout() {
|
||
await supabase.auth.signOut();
|
||
router.push("/kunden/login");
|
||
}
|
||
|
||
if (loading) {
|
||
return (
|
||
<div className="min-h-[60dvh] flex items-center justify-center">
|
||
<p className="text-slate-400">Wird geladen…</p>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<div className="max-w-4xl mx-auto px-4 py-10">
|
||
{/* Header */}
|
||
<div className="flex items-start justify-between gap-4 mb-8">
|
||
<div>
|
||
<h1 className="text-2xl font-bold text-slate-900 tracking-tight">Mein Bereich</h1>
|
||
<p className="text-slate-500 text-sm mt-1">{user?.email}</p>
|
||
</div>
|
||
<div className="flex items-center gap-2">
|
||
<Link
|
||
href="/kunden/profil"
|
||
className="inline-flex items-center gap-1.5 text-sm border border-slate-200 rounded-md px-3 py-1.5 text-slate-600 hover:text-[#1c1917] hover:bg-slate-50 transition-colors"
|
||
>
|
||
<UserCog className="w-3.5 h-3.5" />
|
||
Profil
|
||
</Link>
|
||
<Button
|
||
variant="outline"
|
||
size="sm"
|
||
onClick={handleLogout}
|
||
className="border-slate-200 rounded-md text-slate-600 hover:text-[#1c1917] flex items-center gap-1.5"
|
||
>
|
||
<LogOut className="w-3.5 h-3.5" />
|
||
Abmelden
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
|
||
<Separator className="mb-8" />
|
||
|
||
{/* Anfragen */}
|
||
<div>
|
||
<div className="flex items-center justify-between mb-5">
|
||
<h2 className="font-semibold text-slate-900">Meine Anfragen</h2>
|
||
<Link
|
||
href="/mietpark"
|
||
className="text-sm text-[#1c1917] hover:underline underline-offset-2 font-medium"
|
||
>
|
||
+ Neue Anfrage
|
||
</Link>
|
||
</div>
|
||
|
||
{anfragen.length === 0 ? (
|
||
<div className="border border-dashed border-slate-200 py-12 text-center">
|
||
<Package className="w-8 h-8 text-slate-300 mx-auto mb-3" />
|
||
<p className="text-slate-500 mb-1">Noch keine Anfragen vorhanden</p>
|
||
<p className="text-sm text-slate-400 mb-4">
|
||
Stöbern Sie im Mietpark und stellen Sie Ihre erste Anfrage.
|
||
</p>
|
||
<Link
|
||
href="/mietpark"
|
||
className="inline-flex items-center justify-center bg-[#f7d334] hover:bg-[#fcd34d] text-[#1c1917] rounded-md font-semibold px-5 py-2 text-sm transition-all duration-200 hover:scale-110"
|
||
>
|
||
Zum Mietpark
|
||
</Link>
|
||
</div>
|
||
) : (
|
||
<div className="space-y-4">
|
||
{anfragen.map((anfrage) => {
|
||
const st = STATUS_MAP[anfrage.status] ?? STATUS_MAP.offen;
|
||
const Icon = st.icon;
|
||
return (
|
||
<div key={anfrage.id} className="border border-slate-200 bg-white">
|
||
{/* Anfrage-Header */}
|
||
<div className="flex items-center justify-between gap-3 px-4 py-3 border-b border-slate-100">
|
||
<div className="flex items-center gap-2.5">
|
||
<span className={`inline-flex items-center gap-1.5 text-xs font-medium px-2.5 py-1 border rounded-full ${st.color}`}>
|
||
<Icon className="w-3 h-3" />
|
||
{st.label}
|
||
</span>
|
||
<span className="text-xs text-slate-400">
|
||
{formatDatum(anfrage.created_at)}
|
||
</span>
|
||
</div>
|
||
<span className="text-xs text-slate-300 font-mono hidden sm:block">
|
||
#{anfrage.id.slice(0, 8)}
|
||
</span>
|
||
</div>
|
||
|
||
{/* Positionen */}
|
||
<div className="divide-y divide-slate-50">
|
||
{anfrage.anfragen_positionen.map((pos) => (
|
||
<div key={pos.id} className="px-4 py-3 flex items-center justify-between gap-4">
|
||
<div>
|
||
<p className="font-medium text-sm text-slate-900">{pos.maschine_name}</p>
|
||
<p className="text-xs text-slate-400 mt-0.5">
|
||
{formatDatum(pos.mietbeginn)} – {formatDatum(pos.mietende)} · {pos.gesamt_tage} Tag{pos.gesamt_tage !== 1 ? "e" : ""}
|
||
{pos.lieferung && " · Lieferung"}
|
||
</p>
|
||
</div>
|
||
<span className="font-mono text-sm font-semibold text-[#1c1917] flex-shrink-0">
|
||
{pos.tagessatz != null
|
||
? `${(pos.tagessatz * pos.gesamt_tage).toLocaleString("de-DE")} €`
|
||
: "Auf Anfrage"}
|
||
</span>
|
||
</div>
|
||
))}
|
||
</div>
|
||
|
||
{/* Notiz vom Verleih */}
|
||
{anfrage.notizen && (
|
||
<div className="px-4 py-3 bg-[#fef3c7] border-t border-amber-100">
|
||
<div className="flex items-start gap-2">
|
||
<AlertCircle className="w-4 h-4 text-amber-600 flex-shrink-0 mt-0.5" />
|
||
<p className="text-sm text-amber-800">{anfrage.notizen}</p>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
})}
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|