since3-portal/modules/admin/ui/components/clients-table.tsx
2026-09-29 16:15:28 +02:00

127 lines
5.5 KiB
TypeScript

"use client"
import { useMemo, useState } from "react";
import Link from "next/link";
import { ArrowRightIcon, Building2Icon, Search } from "lucide-react";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { InputGroup, InputGroupAddon, InputGroupInput } from "@/components/ui/input-group";
import { AddClientDialog } from "./add-client-dialog";
import { ClientActionsMenu } from "./client-actions-menu";
type Client = {
id: string;
name: string;
email: string;
};
const STATUS_CONFIG = {
current: { label: "Current", variant: "warning" as const },
done: { label: "Done", variant: "success" as const },
upcoming: { label: "Upcoming", variant: "neutral" as const },
};
// TODO: Status kommt aktuell nicht aus der DB, sobald Projekte existieren durch echten Wert ersetzen.
function getPlaceholderStatus(index: number): keyof typeof STATUS_CONFIG {
const statuses: (keyof typeof STATUS_CONFIG)[] = ["current", "done", "upcoming"];
return statuses[index % statuses.length];
}
export function ClientsTable({ clients }: { clients: Client[] }) {
const [query, setQuery] = useState("");
const filteredClients = useMemo(() => {
const normalizedQuery = query.trim().toLowerCase();
if (!normalizedQuery) return clients;
return clients.filter(
(client) =>
client.name.toLowerCase().includes(normalizedQuery) ||
client.email.toLowerCase().includes(normalizedQuery)
);
}, [clients, query]);
return (
<div className="flex flex-col gap-8 w-full">
<div className="flex flex-row items-center justify-between gap-2 w-full p-2">
<InputGroup className="max-w-lg rounded-md focus-visible:ring-0">
<InputGroupInput
placeholder="Search..."
value={query}
onChange={(event) => setQuery(event.target.value)}
/>
<InputGroupAddon>
<Search />
</InputGroupAddon>
</InputGroup>
<AddClientDialog />
</div>
{filteredClients.length === 0 ? (
<p className="p-8 text-center text-muted-foreground">
{clients.length === 0 ? "Noch keine Clients vorhanden." : "Keine Clients gefunden."}
</p>
) : (
<Table>
<TableHeader>
<TableRow>
<TableHead>Client</TableHead>
<TableHead>Status</TableHead>
<TableHead className="w-24" />
</TableRow>
</TableHeader>
<TableBody>
{filteredClients.map((client) => {
const index = clients.indexOf(client);
const status = STATUS_CONFIG[getPlaceholderStatus(index)];
return (
<TableRow key={client.id}>
<TableCell>
<div className="flex items-center gap-3">
<Avatar size="lg" className="shrink-0">
<AvatarFallback>
<Building2Icon className="size-4.5 text-muted-foreground" />
</AvatarFallback>
</Avatar>
<div className="flex min-w-0 flex-col">
<span className="truncate font-medium">{client.name}</span>
<span className="truncate text-small text-muted-foreground">{client.email}</span>
</div>
</div>
</TableCell>
<TableCell>
<Badge variant={status.variant}>{status.label}</Badge>
</TableCell>
<TableCell>
<div className="flex justify-end gap-1">
<ClientActionsMenu client={client} />
<Button
variant="ghost"
size="icon-sm"
nativeButton={false}
render={<Link href={`/admin/clients/${client.id}`} />}
aria-label={`${client.name} öffnen`}
>
<ArrowRightIcon />
</Button>
</div>
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
)}
</div>
);
}