"use client"; import { useCallback, useEffect, useMemo, useState } from "react"; import Link from "next/link"; import { Edit, Eye, Plus, Trash2 } from "lucide-react"; import ConfirmDialog from "@/components/common/ConfirmDialog"; import DataTable, { type DataTableColumn } from "@/components/common/DataTable"; import SearchInput from "@/components/common/SearchInput"; import { useToast } from "@/components/common/ToastProvider"; import { Button, buttonVariants } from "@/components/ui/button"; import CustomerFormDialog from "@/components/customers/CustomerFormDialog"; import CustomerStatusBadge from "@/components/customers/CustomerStatusBadge"; import { api } from "@/lib/api"; import { hasPermission } from "@/lib/permissions"; import type { CurrentUser } from "@/types/rbac"; import type { Customer, CustomerPayload, CustomerStatus, CustomerType } from "@/types/customer"; const pageSize = 10; function getErrorMessage(error: unknown) { if (typeof error === "object" && error !== null && "response" in error) { const response = (error as { response?: { data?: { detail?: string } } }).response; return response?.data?.detail ?? "Aktion konnte nicht abgeschlossen werden"; } return "Aktion konnte nicht abgeschlossen werden"; } function formatDate(value: string) { return new Intl.DateTimeFormat("de-DE", { day: "2-digit", month: "2-digit", year: "numeric", }).format(new Date(value)); } function getPrimaryCity(customer: Customer) { return customer.addresses.find((address) => address.is_primary)?.city || customer.addresses[0]?.city || "-"; } function getSortableValue(customer: Customer, key: string) { if (key === "city") { return getPrimaryCity(customer).toLowerCase(); } const value = customer[key as keyof Customer]; return typeof value === "string" ? value.toLowerCase() : value; } export default function CustomersPage() { const { showToast } = useToast(); const [customers, setCustomers] = useState([]); const [currentUser, setCurrentUser] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(""); const [formError, setFormError] = useState(""); const [deleteError, setDeleteError] = useState(""); const [pending, setPending] = useState(false); const [search, setSearch] = useState(""); const [statusFilter, setStatusFilter] = useState("all"); const [typeFilter, setTypeFilter] = useState("all"); const [sortKey, setSortKey] = useState("created_at"); const [sortDirection, setSortDirection] = useState<"asc" | "desc">("desc"); const [page, setPage] = useState(1); const [formOpen, setFormOpen] = useState(false); const [selectedCustomer, setSelectedCustomer] = useState(null); const [deleteCustomer, setDeleteCustomer] = useState(null); const loadCustomers = useCallback(async () => { try { const [customersResponse, meResponse] = await Promise.all([ api.get("/customers"), api.get("/me"), ]); setCustomers(customersResponse.data); setCurrentUser(meResponse.data); } catch (err) { setError(getErrorMessage(err)); } finally { setLoading(false); } }, []); useEffect(() => { queueMicrotask(() => { void loadCustomers(); }); }, [loadCustomers]); const filteredCustomers = useMemo(() => { const term = search.trim().toLowerCase(); return customers .filter((customer) => { if (!term) { return true; } return [ customer.customer_number, customer.company_name, customer.legal_name, customer.industry, customer.email, customer.phone, getPrimaryCity(customer), ].some((value) => value.toLowerCase().includes(term)); }) .filter((customer) => statusFilter === "all" || customer.status === statusFilter) .filter((customer) => typeFilter === "all" || customer.customer_type === typeFilter) .sort((first, second) => { const firstValue = getSortableValue(first, sortKey); const secondValue = getSortableValue(second, sortKey); if (firstValue === secondValue) { return 0; } const result = firstValue > secondValue ? 1 : -1; return sortDirection === "asc" ? result : -result; }); }, [customers, search, sortDirection, sortKey, statusFilter, typeFilter]); const pageCount = Math.max(1, Math.ceil(filteredCustomers.length / pageSize)); const currentPage = Math.min(page, pageCount); const pageCustomers = filteredCustomers.slice((currentPage - 1) * pageSize, currentPage * pageSize); const columns: DataTableColumn[] = [ { key: "customer_number", label: "Kundennummer", sortable: true, render: (customer) => customer.customer_number }, { key: "company_name", label: "Firmenname", sortable: true, render: (customer) => {customer.company_name} }, { key: "customer_type", label: "Typ", sortable: true, render: (customer) => customer.customer_type }, { key: "status", label: "Status", sortable: true, render: (customer) => }, { key: "industry", label: "Branche", sortable: true, render: (customer) => customer.industry || "-" }, { key: "email", label: "E-Mail", sortable: true, render: (customer) => customer.email || "-" }, { key: "phone", label: "Telefon", sortable: true, render: (customer) => customer.phone || "-" }, { key: "city", label: "Ort", sortable: true, render: (customer) => getPrimaryCity(customer) }, { key: "created_at", label: "Erstellt", sortable: true, render: (customer) => formatDate(customer.created_at) }, { key: "actions", label: "Aktionen", className: "px-4 py-3 text-right", render: (customer) => (
{hasPermission(currentUser, "customers.update") && ( )} {hasPermission(currentUser, "customers.delete") && ( )}
), }, ]; function handleSort(key: string) { if (sortKey === key) { setSortDirection((current) => (current === "asc" ? "desc" : "asc")); return; } setSortKey(key); setSortDirection("asc"); } function openCreateDialog() { setSelectedCustomer(null); setFormError(""); setFormOpen(true); } function openEditDialog(customer: Customer) { setSelectedCustomer(customer); setFormError(""); setFormOpen(true); } async function saveCustomer(payload: CustomerPayload) { setPending(true); setFormError(""); try { if (selectedCustomer) { const response = await api.put(`/customers/${selectedCustomer.id}`, payload); setCustomers((current) => current.map((customer) => customer.id === selectedCustomer.id ? response.data : customer)); showToast({ type: "success", title: "Kunde aktualisiert", description: response.data.company_name }); } else { const response = await api.post("/customers", payload); setCustomers((current) => [response.data, ...current]); showToast({ type: "success", title: "Kunde erstellt", description: response.data.company_name }); } setFormOpen(false); } catch (err) { const message = getErrorMessage(err); setFormError(message); showToast({ type: "error", title: "Kunde konnte nicht gespeichert werden", description: message }); } finally { setPending(false); } } async function confirmDelete() { if (!deleteCustomer) { return; } setPending(true); setDeleteError(""); try { await api.delete(`/customers/${deleteCustomer.id}`); setCustomers((current) => current.filter((customer) => customer.id !== deleteCustomer.id)); showToast({ type: "success", title: "Kunde gelöscht", description: deleteCustomer.company_name }); setDeleteCustomer(null); } catch (err) { const message = getErrorMessage(err); setDeleteError(message); showToast({ type: "error", title: "Kunde konnte nicht gelöscht werden", description: message }); } finally { setPending(false); } } return (

Kunden

{filteredCustomers.length} von {customers.length} Kunden

{hasPermission(currentUser, "customers.create") && ( )}
{ setSearch(value); setPage(1); }} placeholder="Kunden suchen" />
customer.id} sortKey={sortKey} sortDirection={sortDirection} loading={loading} error={error} emptyTitle="Keine Kunden gefunden" emptyDescription="Passe Suche oder Filter an oder erstelle einen neuen Kunden." onSort={handleSort} />
Seite {currentPage} von {pageCount}
{ if (!open) { setDeleteCustomer(null); setDeleteError(""); } }} onConfirm={confirmDelete} > {deleteCustomer && (
Kundennummer
{deleteCustomer.customer_number}
Firma
{deleteCustomer.company_name}
{deleteError &&

{deleteError}

}
)}
); }