MBO-Tech-IT-Webseite/modules/06-kunden-portal/files/app/kunden/dashboard/page.tsx

204 lines
7.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"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>
);
}