mirror of
https://github.com/Since-3/since3-portal.git
synced 2026-10-12 21:41:16 +00:00
116 lines
5.2 KiB
TypeScript
116 lines
5.2 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 { CLIENT_STATUS_CONFIG, type ClientStatus } from "@/modules/admin/lib/client-status";
|
|
import { ClientActionsMenu } from "./client-actions-menu";
|
|
|
|
type Client = {
|
|
id: string;
|
|
name: string;
|
|
email: string;
|
|
status: ClientStatus;
|
|
};
|
|
|
|
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 w-full flex-col gap-6 md:gap-8">
|
|
<div className="flex w-full flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
|
<InputGroup className="w-full rounded-md focus-visible:ring-0 sm:max-w-lg">
|
|
<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 className="hidden sm:table-cell">Status</TableHead>
|
|
<TableHead className="w-20 sm:w-24" />
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{filteredClients.map((client) => {
|
|
const status = CLIENT_STATUS_CONFIG[client.status];
|
|
|
|
return (
|
|
<TableRow key={client.id}>
|
|
<TableCell className="w-full max-w-0 sm:w-auto sm:max-w-none">
|
|
<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 className="hidden sm:table-cell">
|
|
<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>
|
|
);
|
|
}
|