2025-08-11 18:49:55 +01:00
|
|
|
import React, { useMemo } from 'react';
|
|
|
|
import { Box, Stack, Text } from '@mantine/core';
|
|
|
|
import { type ToolRegistryEntry } from '../../data/toolRegistry';
|
|
|
|
import ToolButton from './toolPicker/ToolButton';
|
2025-08-15 18:12:13 +01:00
|
|
|
import { useTranslation } from 'react-i18next';
|
2025-08-11 18:49:55 +01:00
|
|
|
|
|
|
|
interface SearchResultsProps {
|
|
|
|
filteredTools: [string, ToolRegistryEntry][];
|
|
|
|
onSelect: (id: string) => void;
|
|
|
|
}
|
|
|
|
|
|
|
|
const SearchResults: React.FC<SearchResultsProps> = ({ filteredTools, onSelect }) => {
|
2025-08-15 18:12:13 +01:00
|
|
|
const { t } = useTranslation();
|
2025-08-15 17:48:19 +01:00
|
|
|
// Group tools by subcategory and remove duplicates
|
|
|
|
const groupedToolsByCategory = useMemo(() => {
|
|
|
|
const categoryToToolsMap: Record<string, Array<{ id: string; tool: ToolRegistryEntry }>> = {};
|
|
|
|
const processedToolIds = new Set<string>();
|
2025-08-11 18:49:55 +01:00
|
|
|
|
2025-08-15 17:48:19 +01:00
|
|
|
// Process each tool, skipping duplicates and grouping by subcategory
|
|
|
|
filteredTools.forEach(([toolId, toolEntry]) => {
|
|
|
|
// Skip if we've already processed this tool ID (deduplication)
|
|
|
|
if (processedToolIds.has(toolId)) return;
|
|
|
|
processedToolIds.add(toolId);
|
|
|
|
|
|
|
|
// Use subcategory or default to 'General' if not specified
|
|
|
|
const categoryName = toolEntry?.subcategory || 'General';
|
|
|
|
|
|
|
|
// Initialize category array if it doesn't exist
|
|
|
|
if (!categoryToToolsMap[categoryName]) {
|
|
|
|
categoryToToolsMap[categoryName] = [];
|
|
|
|
}
|
|
|
|
|
|
|
|
categoryToToolsMap[categoryName].push({ id: toolId, tool: toolEntry });
|
2025-08-11 18:49:55 +01:00
|
|
|
});
|
|
|
|
|
2025-08-15 17:48:19 +01:00
|
|
|
// Convert to sorted array format for rendering
|
|
|
|
return Object.entries(categoryToToolsMap)
|
|
|
|
.sort(([categoryA], [categoryB]) => categoryA.localeCompare(categoryB))
|
|
|
|
.map(([categoryName, toolsInCategory]) => ({
|
|
|
|
categoryName,
|
|
|
|
toolsInCategory
|
2025-08-11 18:49:55 +01:00
|
|
|
}));
|
|
|
|
}, [filteredTools]);
|
|
|
|
|
2025-08-15 17:48:19 +01:00
|
|
|
if (groupedToolsByCategory.length === 0) {
|
2025-08-11 18:49:55 +01:00
|
|
|
return (
|
|
|
|
<Text c="dimmed" size="sm" p="sm">
|
2025-08-15 18:12:13 +01:00
|
|
|
{t('toolPicker.noToolsFound', 'No tools found')}
|
2025-08-11 18:49:55 +01:00
|
|
|
</Text>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<Stack p="sm" gap="xs">
|
2025-08-15 17:48:19 +01:00
|
|
|
{groupedToolsByCategory.map(categoryGroup => (
|
|
|
|
<Box key={categoryGroup.categoryName} w="100%">
|
2025-08-11 18:49:55 +01:00
|
|
|
<Text size="sm" fw={500} mb="0.25rem" mt="1rem" className="tool-subcategory-title">
|
2025-08-15 18:12:13 +01:00
|
|
|
{t(`toolPicker.subcategories.${categoryGroup.categoryName}`, categoryGroup.categoryName)}
|
2025-08-11 18:49:55 +01:00
|
|
|
</Text>
|
|
|
|
<Stack gap="xs">
|
2025-08-15 17:48:19 +01:00
|
|
|
{categoryGroup.toolsInCategory.map(({ id, tool }) => (
|
2025-08-11 18:49:55 +01:00
|
|
|
<ToolButton
|
|
|
|
key={id}
|
|
|
|
id={id}
|
|
|
|
tool={tool}
|
|
|
|
isSelected={false}
|
|
|
|
onSelect={onSelect}
|
|
|
|
/>
|
|
|
|
))}
|
|
|
|
</Stack>
|
|
|
|
{/* bottom spacer within each group not strictly required, outer list can add a spacer if needed */}
|
|
|
|
</Box>
|
|
|
|
))}
|
|
|
|
{/* global spacer to allow scrolling past last row in search mode */}
|
2025-08-15 17:48:19 +01:00
|
|
|
<div aria-hidden style={{ height: 200 }} />
|
2025-08-11 18:49:55 +01:00
|
|
|
</Stack>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
export default SearchResults;
|
|
|
|
|
|
|
|
|