'use client'; import { useEffect, useRef, useState, useTransition } from 'react'; import Link from 'next/link'; import { useRouter } from 'next/navigation'; import { Download, Filter, Plus, Search, X } from 'lucide-react'; import { useAction } from 'next-safe-action/hooks'; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from '@kit/ui/alert-dialog'; import { Badge } from '@kit/ui/badge'; import { Button } from '@kit/ui/button'; import { Input } from '@kit/ui/input'; import { Popover, PopoverContent, PopoverTrigger } from '@kit/ui/popover'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@kit/ui/select'; import { toast } from '@kit/ui/sonner'; import { useFileDownloadAction } from '@kit/ui/use-file-download-action'; import { exportMembers, exportMembersExcel, bulkUpdateStatus, bulkArchiveMembers, } from '../server/actions/member-actions'; import { MembersFilterPanel } from './members-filter-panel'; const STATUS_OPTIONS = [ { value: '', label: 'Alle Status' }, { value: 'active', label: 'Aktiv' }, { value: 'inactive', label: 'Inaktiv' }, { value: 'pending', label: 'Ausstehend' }, { value: 'resigned', label: 'Ausgetreten' }, ] as const; interface MembersToolbarProps { account: string; accountId: string; searchValue: string; statusFilter: string; onSearchChange: (value: string) => void; onStatusChange: (value: string) => void; selectedCount: number; selectedIds: string[]; departments: Array<{ id: string; name: string; memberCount: number }>; duesCategories: Array<{ id: string; name: string }>; tags?: Array<{ id: string; name: string; color: string }>; } export function MembersToolbar({ account, accountId, searchValue, statusFilter, onSearchChange, onStatusChange, selectedCount, selectedIds, departments, duesCategories, tags = [], }: MembersToolbarProps) { const router = useRouter(); const [, startTransition] = useTransition(); const [searchInput, setSearchInput] = useState(searchValue); const [filterOpen, setFilterOpen] = useState(false); const [archiveDialogOpen, setArchiveDialogOpen] = useState(false); const debounceRef = useRef>(null); const { execute: csvDownload, isPending: isCsvDownloading } = useFileDownloadAction(exportMembers); const { execute: excelDownload, isPending: isExcelDownloading } = useFileDownloadAction(exportMembersExcel); // Debounced search — auto-triggers after 300ms useEffect(() => { if (searchInput === searchValue) return; if (debounceRef.current) clearTimeout(debounceRef.current); debounceRef.current = setTimeout(() => { onSearchChange(searchInput); }, 300); return () => { if (debounceRef.current) clearTimeout(debounceRef.current); }; }, [searchInput, searchValue, onSearchChange]); const { execute: executeBulkStatus } = useAction(bulkUpdateStatus, { onSuccess: () => { toast.success('Status aktualisiert'); startTransition(() => router.refresh()); }, onError: () => toast.error('Fehler beim Aktualisieren'), }); const { execute: executeBulkArchive } = useAction(bulkArchiveMembers, { onSuccess: () => { toast.success('Mitglieder archiviert'); setArchiveDialogOpen(false); startTransition(() => router.refresh()); }, onError: () => toast.error('Fehler beim Archivieren'), }); // Detect OS for shortcut hint const isMac = typeof navigator !== 'undefined' && navigator.userAgent.includes('Mac'); const shortcutHint = isMac ? '⌘K' : 'Ctrl+K'; return (
{/* Main toolbar */}
{/* Search */}
setSearchInput(e.target.value)} className="pr-16 pl-9" data-test="members-search" />
{searchInput ? ( ) : ( {shortcutHint} )}
{/* Status filter */} {/* Advanced filter */} Filter setFilterOpen(false)} />
{/* Export — collapsed on mobile */}
{/* Create */} Neues Mitglied Neu
{/* Bulk actions bar */} {selectedCount > 0 && (
{selectedCount} ausgewählt
)} {/* Archive confirmation dialog */} Mitglieder archivieren Möchten Sie {selectedCount} Mitglied {selectedCount > 1 ? 'er' : ''} wirklich archivieren? Abbrechen executeBulkArchive({ memberIds: selectedIds, accountId }) } > Archivieren
); }