feat(customers): add initial admin bootstrap and csv import

This commit is contained in:
DS | Schubert 2026-07-03 09:14:56 +02:00
parent c816e9869d
commit ecab0fe6b6
27 changed files with 1197 additions and 34 deletions

View file

@ -2,7 +2,7 @@
import { useCallback, useEffect, useMemo, useState } from "react";
import Link from "next/link";
import { Edit, Eye, Plus, Trash2 } from "lucide-react";
import { Edit, Eye, Plus, Trash2, Upload } from "lucide-react";
import ConfirmDialog from "@/components/common/ConfirmDialog";
import DataTable, { type DataTableColumn } from "@/components/common/DataTable";
@ -10,11 +10,12 @@ 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 CustomerImportDialog from "@/components/customers/CustomerImportDialog";
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";
import type { Customer, CustomerImportCommitResponse, CustomerPayload, CustomerStatus, CustomerType } from "@/types/customer";
const pageSize = 10;
@ -64,6 +65,7 @@ export default function CustomersPage() {
const [sortDirection, setSortDirection] = useState<"asc" | "desc">("desc");
const [page, setPage] = useState(1);
const [formOpen, setFormOpen] = useState(false);
const [importOpen, setImportOpen] = useState(false);
const [selectedCustomer, setSelectedCustomer] = useState<Customer | null>(null);
const [deleteCustomer, setDeleteCustomer] = useState<Customer | null>(null);
@ -122,6 +124,7 @@ export default function CustomersPage() {
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 canImportCustomers = hasPermission(currentUser, "customers.create") || hasPermission(currentUser, "customers.update");
const columns: DataTableColumn<Customer>[] = [
{ key: "customer_number", label: "Kundennummer", sortable: true, render: (customer) => customer.customer_number },
@ -221,6 +224,16 @@ export default function CustomersPage() {
}
}
async function handleImportCommitted(result: CustomerImportCommitResponse) {
showToast({
type: "success",
title: "Kundenimport abgeschlossen",
description: `${result.created} erstellt, ${result.updated} aktualisiert, ${result.skipped} übersprungen`,
});
setLoading(true);
await loadCustomers();
}
return (
<div className="space-y-6">
<div className="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
@ -228,12 +241,20 @@ export default function CustomersPage() {
<h1 className="text-3xl font-bold text-slate-950">Kunden</h1>
<p className="mt-1 text-sm text-slate-500">{filteredCustomers.length} von {customers.length} Kunden</p>
</div>
{hasPermission(currentUser, "customers.create") && (
<Button type="button" onClick={openCreateDialog}>
<Plus size={16} />
Neuer Kunde
</Button>
)}
<div className="flex flex-wrap gap-2">
{canImportCustomers && (
<Button type="button" variant="outline" onClick={() => setImportOpen(true)}>
<Upload size={16} />
Import
</Button>
)}
{hasPermission(currentUser, "customers.create") && (
<Button type="button" onClick={openCreateDialog}>
<Plus size={16} />
Neuer Kunde
</Button>
)}
</div>
</div>
<div className="flex flex-col gap-3 rounded-lg border bg-white p-4 lg:flex-row lg:items-center">
@ -286,6 +307,13 @@ export default function CustomersPage() {
onSubmit={saveCustomer}
/>
<CustomerImportDialog
open={importOpen}
onOpenChange={setImportOpen}
onCommitted={handleImportCommitted}
onError={(message) => showToast({ type: "error", title: "Kundenimport fehlgeschlagen", description: message })}
/>
<ConfirmDialog
open={Boolean(deleteCustomer)}
title="Kunde löschen"