import { CheckSquare, FolderTree, Search, Square } from 'lucide-react'; import React, { useMemo, useState } from 'react'; import { useI18n } from '@/application/i18n/I18nProvider'; import type { Host } from '@/domain/models'; import { Button } from '@/components/ui/button'; import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { Input } from '@/components/ui/input'; import { FixedSizeVirtualList } from '@/components/ui/FixedSizeVirtualList'; import { cn } from '@/lib/utils'; export interface SelectGroupDialogProps { open: boolean; onOpenChange: (open: boolean) => void; hosts: Host[]; customGroups?: string[]; selectedGroupPaths: string[]; onSelectionChange: (selectedGroupPaths: string[]) => void; } const GROUP_ROW_HEIGHT = 48; function collectGroupPaths( hosts: Host[], customGroups: string[], selectedGroupPaths: string[], ): string[] { const paths = new Set(); for (const groupPath of [...customGroups, ...selectedGroupPaths]) { const parts = groupPath.split('/').filter(Boolean); for (let index = 1; index <= parts.length; index += 1) { paths.add(parts.slice(0, index).join('/')); } } for (const host of hosts) { const parts = host.group?.split('/').filter(Boolean) ?? []; for (let index = 1; index <= parts.length; index += 1) { paths.add(parts.slice(0, index).join('/')); } } return [...paths].sort((left, right) => left.localeCompare(right)); } function countHostsByGroupPath(hosts: Host[]): Map { const counts = new Map(); for (const host of hosts) { if (host.protocol === 'serial') continue; const parts = host.group?.split('/').filter(Boolean) ?? []; for (let index = 1; index <= parts.length; index += 1) { const path = parts.slice(0, index).join('/'); counts.set(path, (counts.get(path) ?? 0) + 1); } } return counts; } export const SelectGroupDialog: React.FC = ({ open, onOpenChange, hosts, customGroups = [], selectedGroupPaths, onSelectionChange, }) => { const { t } = useI18n(); const [searchQuery, setSearchQuery] = useState(''); const selected = useMemo(() => new Set(selectedGroupPaths), [selectedGroupPaths]); const groupPaths = useMemo( () => collectGroupPaths(hosts, customGroups, selectedGroupPaths), [customGroups, hosts, selectedGroupPaths], ); const displayedPaths = useMemo(() => { const query = searchQuery.trim().toLocaleLowerCase(); if (!query) return groupPaths; return groupPaths.filter((path) => path.toLocaleLowerCase().includes(query)); }, [groupPaths, searchQuery]); const hostCountByPath = useMemo(() => countHostsByGroupPath(hosts), [hosts]); const togglePath = (path: string) => { if (selected.has(path)) { onSelectionChange(selectedGroupPaths.filter((candidate) => candidate !== path)); return; } onSelectionChange([...selectedGroupPaths, path]); }; return ( {t('snippets.targets.selectGroups')}
setSearchQuery(event.target.value)} placeholder={t('common.searchPlaceholder')} className="h-8 pl-8" />
{displayedPaths.length === 0 ? (

{t('snippets.targets.noGroups')}

) : ( path} renderItem={(path) => { const isSelected = selected.has(path); const depth = Math.max(0, path.split('/').filter(Boolean).length - 1); const label = path.split('/').filter(Boolean).pop() ?? path; const hostCount = hostCountByPath.get(path) ?? 0; return ( ); }} /> )}
); };