diff --git a/frontend/public/locales/en-GB/translation.json b/frontend/public/locales/en-GB/translation.json index e09f874ac..0a03fd01a 100644 --- a/frontend/public/locales/en-GB/translation.json +++ b/frontend/public/locales/en-GB/translation.json @@ -1925,18 +1925,23 @@ "noToolsFound": "No tools found", "allTools": "ALL TOOLS", "quickAccess": "QUICK ACCESS", + "categories": { + "standardTools": "Standard Tools", + "advancedTools": "Advanced Tools", + "recommendedTools": "Recommended Tools" + }, "subcategories": { - "Signing": "Signing", - "Document Security": "Document Security", - "Verification": "Verification", - "Document Review": "Document Review", - "Page Formatting": "Page Formatting", - "Extraction": "Extraction", - "Removal": "Removal", - "Automation": "Automation", - "General": "General", - "Advanced Formatting": "Advanced Formatting", - "Developer Tools": "Developer Tools" + "signing": "Signing", + "documentSecurity": "Document Security", + "verification": "Verification", + "documentReview": "Document Review", + "pageFormatting": "Page Formatting", + "extraction": "Extraction", + "removal": "Removal", + "automation": "Automation", + "general": "General", + "advancedFormatting": "Advanced Formatting", + "developerTools": "Developer Tools" } }, "quickAccess": { diff --git a/frontend/src/components/shared/QuickAccessBar.tsx b/frontend/src/components/shared/QuickAccessBar.tsx index bc041a923..80ef86c83 100644 --- a/frontend/src/components/shared/QuickAccessBar.tsx +++ b/frontend/src/components/shared/QuickAccessBar.tsx @@ -13,9 +13,9 @@ import { ButtonConfig } from '../../types/sidebar'; import './quickAccessBar/QuickAccessBar.css'; import AllToolsNavButton from './AllToolsNavButton'; import ActiveToolButton from "./quickAccessBar/ActiveToolButton"; -import { - isNavButtonActive, - getNavButtonStyle, +import { + isNavButtonActive, + getNavButtonStyle, getActiveNavButton, } from './quickAccessBar/QuickAccessBar'; @@ -39,7 +39,7 @@ const QuickAccessBar = forwardRef(({ openFilesModal(); }; - + const buttonConfigs: ButtonConfig[] = [ { id: 'read', @@ -226,4 +226,4 @@ const QuickAccessBar = forwardRef(({ ); }); -export default QuickAccessBar; \ No newline at end of file +export default QuickAccessBar; diff --git a/frontend/src/components/tools/SearchResults.tsx b/frontend/src/components/tools/SearchResults.tsx index 949bd2f64..6ecb64b7e 100644 --- a/frontend/src/components/tools/SearchResults.tsx +++ b/frontend/src/components/tools/SearchResults.tsx @@ -1,6 +1,6 @@ import React, { useMemo } from 'react'; import { Box, Stack, Text } from '@mantine/core'; -import { ToolRegistryEntry } from '../../data/toolsTaxonomy'; +import { getSubcategoryLabel, ToolRegistryEntry } from '../../data/toolsTaxonomy'; import ToolButton from './toolPicker/ToolButton'; import { useTranslation } from 'react-i18next'; import { useToolSections } from '../../hooks/useToolSections'; @@ -23,8 +23,8 @@ const SearchResults: React.FC = ({ filteredTools, onSelect } return ( {searchGroups.map(group => ( - - + + {group.tools.map(({ id, tool }) => ( void, showSubcategoryHeader: boolean = true ) => ( - + {showSubcategoryHeader && ( - + )} {subcategory.tools.map(({ id, tool }: { id: string; tool: any }) => ( @@ -69,11 +71,11 @@ const ToolPicker = ({ selectedToolKey, onSelect, filteredTools, isSearching = fa const { sections: visibleSections } = useToolSections(filteredTools); const quickSection = useMemo( - () => visibleSections.find(s => (s as any).key === 'quick'), + () => visibleSections.find(s => s.key === 'quick'), [visibleSections] ); const allSection = useMemo( - () => visibleSections.find(s => (s as any).key === 'all'), + () => visibleSections.find(s => s.key === 'all'), [visibleSections] ); @@ -120,7 +122,7 @@ const ToolPicker = ({ selectedToolKey, onSelect, filteredTools, isSearching = fa {searchGroups.length === 0 ? ( ) : ( - searchGroups.map(group => renderToolButtons(group, selectedToolKey, onSelect)) + searchGroups.map(group => renderToolButtons(t, group, selectedToolKey, onSelect)) )} ) : ( @@ -164,8 +166,8 @@ const ToolPicker = ({ selectedToolKey, onSelect, filteredTools, isSearching = fa - {quickSection?.subcategories.map(sc => - renderToolButtons(sc, selectedToolKey, onSelect, false) + {quickSection?.subcategories.map(sc => + renderToolButtons(t, sc, selectedToolKey, onSelect, false) )} @@ -210,8 +212,8 @@ const ToolPicker = ({ selectedToolKey, onSelect, filteredTools, isSearching = fa - {allSection?.subcategories.map(sc => - renderToolButtons(sc, selectedToolKey, onSelect, true) + {allSection?.subcategories.map(sc => + renderToolButtons(t, sc, selectedToolKey, onSelect, true) )} diff --git a/frontend/src/components/tools/shared/SuggestedToolsSection.tsx b/frontend/src/components/tools/shared/SuggestedToolsSection.tsx index b1f91fc39..da0418571 100644 --- a/frontend/src/components/tools/shared/SuggestedToolsSection.tsx +++ b/frontend/src/components/tools/shared/SuggestedToolsSection.tsx @@ -21,7 +21,7 @@ export function SuggestedToolsSection(): React.ReactElement { const IconComponent = tool.icon; return ( = ({ id, tool, isSelected, onSelect }) => { const handleClick = (id: string) => { if (tool.link) { - // Open external link in new tab + // Open external link in new tab window.open(tool.link, '_blank', 'noopener,noreferrer'); - return; + return; } // Normal tool selection onSelect(id); @@ -47,4 +47,4 @@ const ToolButton: React.FC = ({ id, tool, isSelected, onSelect ); }; -export default ToolButton; \ No newline at end of file +export default ToolButton; diff --git a/frontend/src/contexts/ToolWorkflowContext.tsx b/frontend/src/contexts/ToolWorkflowContext.tsx index 4ca66e61c..1ee211312 100644 --- a/frontend/src/contexts/ToolWorkflowContext.tsx +++ b/frontend/src/contexts/ToolWorkflowContext.tsx @@ -72,7 +72,7 @@ interface ToolWorkflowContextValue extends ToolWorkflowState { selectedToolKey: string | null; selectedTool: ToolRegistryEntry | null; toolRegistry: any; // From useToolManagement - + // UI Actions setSidebarsVisible: (visible: boolean) => void; setLeftPanelView: (view: 'toolPicker' | 'toolContent') => void; @@ -230,4 +230,4 @@ export function useToolWorkflow(): ToolWorkflowContextValue { throw new Error('useToolWorkflow must be used within a ToolWorkflowProvider'); } return context; -} \ No newline at end of file +} diff --git a/frontend/src/data/toolsTaxonomy.ts b/frontend/src/data/toolsTaxonomy.ts index a76752b87..2dff2b8e4 100644 --- a/frontend/src/data/toolsTaxonomy.ts +++ b/frontend/src/data/toolsTaxonomy.ts @@ -3,101 +3,101 @@ import React from 'react'; import { BaseToolProps } from '../types/tool'; export enum SubcategoryId { - SIGNING = 'signing', - DOCUMENT_SECURITY = 'documentSecurity', - VERIFICATION = 'verification', - DOCUMENT_REVIEW = 'documentReview', - PAGE_FORMATTING = 'pageFormatting', - EXTRACTION = 'extraction', - REMOVAL = 'removal', - AUTOMATION = 'automation', - GENERAL = 'general', - ADVANCED_FORMATTING = 'advancedFormatting', - DEVELOPER_TOOLS = 'developerTools' + SIGNING = 'signing', + DOCUMENT_SECURITY = 'documentSecurity', + VERIFICATION = 'verification', + DOCUMENT_REVIEW = 'documentReview', + PAGE_FORMATTING = 'pageFormatting', + EXTRACTION = 'extraction', + REMOVAL = 'removal', + AUTOMATION = 'automation', + GENERAL = 'general', + ADVANCED_FORMATTING = 'advancedFormatting', + DEVELOPER_TOOLS = 'developerTools' } -export enum ToolCategory { - STANDARD_TOOLS = 'Standard Tools', - ADVANCED_TOOLS = 'Advanced Tools', - RECOMMENDED_TOOLS = 'Recommended Tools' +export enum ToolCategoryId { + STANDARD_TOOLS = 'standardTools', + ADVANCED_TOOLS = 'advancedTools', + RECOMMENDED_TOOLS = 'recommendedTools' } export type ToolRegistryEntry = { - icon: React.ReactNode; - name: string; - component: React.ComponentType | null; - view: 'sign' | 'security' | 'format' | 'extract' | 'view' | 'merge' | 'pageEditor' | 'convert' | 'redact' | 'split' | 'convert' | 'remove' | 'compress' | 'external'; - description: string; - category: ToolCategory; - subcategory: SubcategoryId; - maxFiles?: number; - supportedFormats?: string[]; - endpoints?: string[]; - link?: string; - type?: string; + icon: React.ReactNode; + name: string; + component: React.ComponentType | null; + view: 'sign' | 'security' | 'format' | 'extract' | 'view' | 'merge' | 'pageEditor' | 'convert' | 'redact' | 'split' | 'convert' | 'remove' | 'compress' | 'external'; + description: string; + categoryId: ToolCategoryId; + subcategoryId: SubcategoryId; + maxFiles?: number; + supportedFormats?: string[]; + endpoints?: string[]; + link?: string; + type?: string; } -export type ToolRegistry = Record; +export type ToolRegistry = Record; export const SUBCATEGORY_ORDER: SubcategoryId[] = [ - SubcategoryId.SIGNING, - SubcategoryId.DOCUMENT_SECURITY, - SubcategoryId.VERIFICATION, - SubcategoryId.DOCUMENT_REVIEW, - SubcategoryId.PAGE_FORMATTING, - SubcategoryId.EXTRACTION, - SubcategoryId.REMOVAL, - SubcategoryId.AUTOMATION, - SubcategoryId.GENERAL, - SubcategoryId.ADVANCED_FORMATTING, - SubcategoryId.DEVELOPER_TOOLS, + SubcategoryId.SIGNING, + SubcategoryId.DOCUMENT_SECURITY, + SubcategoryId.VERIFICATION, + SubcategoryId.DOCUMENT_REVIEW, + SubcategoryId.PAGE_FORMATTING, + SubcategoryId.EXTRACTION, + SubcategoryId.REMOVAL, + SubcategoryId.AUTOMATION, + SubcategoryId.GENERAL, + SubcategoryId.ADVANCED_FORMATTING, + SubcategoryId.DEVELOPER_TOOLS, ]; export const SUBCATEGORY_COLOR_MAP: Record = { - [SubcategoryId.SIGNING]: '#FF7892', - [SubcategoryId.DOCUMENT_SECURITY]: '#FF7892', - [SubcategoryId.VERIFICATION]: '#1BB1D4', - [SubcategoryId.DOCUMENT_REVIEW]: '#48BD54', - [SubcategoryId.PAGE_FORMATTING]: '#7882FF', - [SubcategoryId.EXTRACTION]: '#1BB1D4', - [SubcategoryId.REMOVAL]: '#7882FF', - [SubcategoryId.AUTOMATION]: '#69DC95', - [SubcategoryId.GENERAL]: '#69DC95', - [SubcategoryId.ADVANCED_FORMATTING]: '#F55454', - [SubcategoryId.DEVELOPER_TOOLS]: '#F55454', + [SubcategoryId.SIGNING]: '#FF7892', + [SubcategoryId.DOCUMENT_SECURITY]: '#FF7892', + [SubcategoryId.VERIFICATION]: '#1BB1D4', + [SubcategoryId.DOCUMENT_REVIEW]: '#48BD54', + [SubcategoryId.PAGE_FORMATTING]: '#7882FF', + [SubcategoryId.EXTRACTION]: '#1BB1D4', + [SubcategoryId.REMOVAL]: '#7882FF', + [SubcategoryId.AUTOMATION]: '#69DC95', + [SubcategoryId.GENERAL]: '#69DC95', + [SubcategoryId.ADVANCED_FORMATTING]: '#F55454', + [SubcategoryId.DEVELOPER_TOOLS]: '#F55454', }; -export const getSubcategoryColor = (subcategory: SubcategoryId): string => SUBCATEGORY_COLOR_MAP[subcategory] || '#7882FF'; - +export const getCategoryLabel = (t: TFunction, id: ToolCategoryId): string => t(`toolPicker.categories.${id}`, id); export const getSubcategoryLabel = (t: TFunction, id: SubcategoryId): string => t(`toolPicker.subcategories.${id}`, id); +export const getSubcategoryColor = (subcategory: SubcategoryId): string => SUBCATEGORY_COLOR_MAP[subcategory] || '#7882FF'; export const getAllEndpoints = (registry: ToolRegistry): string[] => { - const lists: string[][] = []; - Object.values(registry).forEach(entry => { - if (entry.endpoints && entry.endpoints.length > 0) { - lists.push(entry.endpoints); - } - }); - return Array.from(new Set(lists.flat())); + const lists: string[][] = []; + Object.values(registry).forEach(entry => { + if (entry.endpoints && entry.endpoints.length > 0) { + lists.push(entry.endpoints); + } + }); + return Array.from(new Set(lists.flat())); }; export const getConversionEndpoints = (extensionToEndpoint: Record>): string[] => { - const endpoints = new Set(); - Object.values(extensionToEndpoint).forEach(toEndpoints => { - Object.values(toEndpoints).forEach(endpoint => { - endpoints.add(endpoint); - }); - }); - return Array.from(endpoints); + const endpoints = new Set(); + Object.values(extensionToEndpoint).forEach(toEndpoints => { + Object.values(toEndpoints).forEach(endpoint => { + endpoints.add(endpoint); + }); + }); + return Array.from(endpoints); }; export const getAllApplicationEndpoints = ( - registry: ToolRegistry, - extensionToEndpoint?: Record> + registry: ToolRegistry, + extensionToEndpoint?: Record> ): string[] => { - const toolEp = getAllEndpoints(registry); - const convEp = extensionToEndpoint ? getConversionEndpoints(extensionToEndpoint) : []; - return Array.from(new Set([...toolEp, ...convEp])); + const toolEp = getAllEndpoints(registry); + const convEp = extensionToEndpoint ? getConversionEndpoints(extensionToEndpoint) : []; + return Array.from(new Set([...toolEp, ...convEp])); }; diff --git a/frontend/src/data/useTranslatedToolRegistry.tsx b/frontend/src/data/useTranslatedToolRegistry.tsx index f27fbd5c7..0426a7bf6 100644 --- a/frontend/src/data/useTranslatedToolRegistry.tsx +++ b/frontend/src/data/useTranslatedToolRegistry.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { useTranslation } from 'react-i18next'; import SplitPdfPanel from "../tools/Split"; import CompressPdfPanel from "../tools/Compress"; @@ -8,7 +7,7 @@ import Sanitize from '../tools/Sanitize'; import AddPassword from '../tools/AddPassword'; import ChangePermissions from '../tools/ChangePermissions'; import RemovePassword from '../tools/RemovePassword'; -import { SubcategoryId, ToolCategory, ToolRegistry } from './toolsTaxonomy'; +import { SubcategoryId, ToolCategoryId, ToolRegistry } from './toolsTaxonomy'; import AddWatermark from '../tools/AddWatermark'; import Repair from '../tools/Repair'; import SingleLargePage from '../tools/SingleLargePage'; @@ -30,8 +29,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "sign", description: t("home.certSign.desc", "Signs a PDF with a Certificate/Key (PEM/P12)"), - category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.SIGNING + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.SIGNING }, "sign": { icon: signature, @@ -39,8 +38,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "sign", description: t("home.sign.desc", "Adds signature to PDF by drawing, text or image"), - category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.SIGNING + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.SIGNING }, @@ -52,8 +51,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: AddPassword, view: "security", description: t("home.addPassword.desc", "Add password protection and restrictions to PDF files"), - category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.DOCUMENT_SECURITY, + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.DOCUMENT_SECURITY, maxFiles: -1, endpoints: ["add-password"] }, @@ -64,8 +63,8 @@ export function useFlatToolRegistry(): ToolRegistry { view: "format", maxFiles: -1, description: t("home.watermark.desc", "Add a custom watermark to your PDF document."), - category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.DOCUMENT_SECURITY, + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.DOCUMENT_SECURITY, endpoints: ["add-watermark"] }, "add-stamp": { @@ -74,8 +73,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "format", description: t("home.AddStampRequest.desc", "Add text or add image stamps at set locations"), - category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.DOCUMENT_SECURITY + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.DOCUMENT_SECURITY }, "sanitize": { icon: cleaning_services, @@ -83,8 +82,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: Sanitize, view: "security", maxFiles: -1, - category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.DOCUMENT_SECURITY, + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.DOCUMENT_SECURITY, description: t("home.sanitize.desc", "Remove potentially harmful elements from PDF files"), endpoints: ["sanitize-pdf"] }, @@ -94,8 +93,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "format", description: t("home.flatten.desc", "Remove all interactive elements and forms from a PDF"), - category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.DOCUMENT_SECURITY + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.DOCUMENT_SECURITY }, "unlock-pdf-forms": { icon: preview_off, @@ -103,8 +102,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: UnlockPdfForms, view: "security", description: t("home.unlockPDFForms.desc", "Remove read-only property of form fields in a PDF document."), - category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.DOCUMENT_SECURITY, + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.DOCUMENT_SECURITY, maxFiles: -1, endpoints: ["unlock-pdf-forms"] }, @@ -114,8 +113,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "security", description: t("home.manageCertificates.desc", "Import, export, or delete digital certificate files used for signing PDFs."), - category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.DOCUMENT_SECURITY + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.DOCUMENT_SECURITY }, "change-permissions": { icon: lock, @@ -123,8 +122,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: ChangePermissions, view: "security", description: t("home.changePermissions.desc", "Change document restrictions and permissions"), - category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.DOCUMENT_SECURITY, + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.DOCUMENT_SECURITY, maxFiles: -1, endpoints: ["add-password"] }, @@ -136,8 +135,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "extract", description: t("home.getPdfInfo.desc", "Grabs any and all information possible on PDFs"), - category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.VERIFICATION + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.VERIFICATION }, "validate-pdf-signature": { icon: verified, @@ -145,8 +144,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "security", description: t("home.validateSignature.desc", "Verify digital signatures and certificates in PDF documents"), - category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.VERIFICATION + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.VERIFICATION }, @@ -158,8 +157,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "view", description: t("home.read.desc", "View and annotate PDFs. Highlight text, draw, or insert comments for review and collaboration."), - category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.DOCUMENT_REVIEW + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.DOCUMENT_REVIEW }, "change-metadata": { icon: assignment, @@ -167,8 +166,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "format", description: t("home.changeMetadata.desc", "Change/Remove/Add metadata from a PDF document"), - category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.DOCUMENT_REVIEW + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.DOCUMENT_REVIEW }, // Page Formatting @@ -178,8 +177,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "format", description: t("home.crop.desc", "Crop a PDF to reduce its size (maintains text!)"), - category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.PAGE_FORMATTING + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.PAGE_FORMATTING }, "rotate": { icon: rotate_right, @@ -187,8 +186,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "format", description: t("home.rotate.desc", "Easily rotate your PDFs."), - category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.PAGE_FORMATTING + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.PAGE_FORMATTING }, "splitPdf": { icon: content_cut, @@ -196,8 +195,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: SplitPdfPanel, view: "split", description: t("home.split.desc", "Split PDFs into multiple documents"), - category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.PAGE_FORMATTING + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.PAGE_FORMATTING }, "reorganize-pages": { icon: move_down, @@ -205,8 +204,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "pageEditor", description: t("home.reorganizePages.desc", "Rearrange, duplicate, or delete PDF pages with visual drag-and-drop control."), - category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.PAGE_FORMATTING + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.PAGE_FORMATTING }, "adjust-page-size-scale": { icon: crop_free, @@ -214,8 +213,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "format", description: t("home.scalePages.desc", "Change the size/scale of a page and/or its contents."), - category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.PAGE_FORMATTING + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.PAGE_FORMATTING }, "addPageNumbers": { icon: 123, @@ -223,8 +222,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "format", description: t("home.addPageNumbers.desc", "Add Page numbers throughout a document in a set location"), - category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.PAGE_FORMATTING + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.PAGE_FORMATTING }, "multi-page-layout": { icon: dashboard, @@ -232,8 +231,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "format", description: t("home.pageLayout.desc", "Merge multiple pages of a PDF document into a single page"), - category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.PAGE_FORMATTING + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.PAGE_FORMATTING }, "single-large-page": { icon: looks_one, @@ -241,8 +240,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: SingleLargePage, view: "format", description: t("home.pdfToSinglePage.desc", "Merges all PDF pages into one large single page"), - category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.PAGE_FORMATTING, + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.PAGE_FORMATTING, maxFiles: -1, endpoints: ["pdf-to-single-page"] }, @@ -252,8 +251,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "format", description: t("home.attachments.desc", "Add or remove embedded files (attachments) to/from a PDF"), - category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.PAGE_FORMATTING, + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.PAGE_FORMATTING, }, @@ -265,8 +264,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "extract", description: t("home.extractPages.desc", "Extract specific pages from a PDF document"), - category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.EXTRACTION + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.EXTRACTION }, "extract-images": { icon: filter, @@ -274,8 +273,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "extract", description: t("home.extractImages.desc", "Extract images from PDF documents"), - category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.EXTRACTION + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.EXTRACTION }, @@ -287,8 +286,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "remove", description: t("home.removePages.desc", "Remove specific pages from a PDF document"), - category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.REMOVAL + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.REMOVAL }, "remove-blank-pages": { icon: scan_delete, @@ -296,8 +295,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "remove", description: t("home.removeBlanks.desc", "Remove blank pages from PDF documents"), - category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.REMOVAL + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.REMOVAL }, "remove-annotations": { icon: thread_unread, @@ -305,8 +304,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "remove", description: t("home.removeAnnotations.desc", "Remove annotations and comments from PDF documents"), - category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.REMOVAL + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.REMOVAL }, "remove-image": { icon: remove_selection, @@ -314,8 +313,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "format", description: t("home.removeImagePdf.desc", "Remove images from PDF documents"), - category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.REMOVAL + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.REMOVAL }, "remove-password": { icon: lock_open_right, @@ -323,8 +322,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: RemovePassword, view: "security", description: t("home.removePassword.desc", "Remove password protection from PDF documents"), - category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.REMOVAL, + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.REMOVAL, endpoints: ["remove-password"], maxFiles: -1, @@ -335,8 +334,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: RemoveCertificateSign, view: "security", description: t("home.removeCertSign.desc", "Remove digital signature from PDF documents"), - category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.REMOVAL, + categoryId: ToolCategoryId.STANDARD_TOOLS, + subcategoryId: SubcategoryId.REMOVAL, maxFiles: -1, endpoints: ["remove-certificate-sign"] }, @@ -350,8 +349,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "format", description: t("home.automate.desc", "Build multi-step workflows by chaining together PDF actions. Ideal for recurring tasks."), - category: ToolCategory.ADVANCED_TOOLS, - subcategory: SubcategoryId.AUTOMATION + categoryId: ToolCategoryId.ADVANCED_TOOLS, + subcategoryId: SubcategoryId.AUTOMATION }, "auto-rename-pdf-file": { icon: match_word, @@ -359,8 +358,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "format", description: t("home.auto-rename.desc", "Automatically rename PDF files based on their content"), - category: ToolCategory.ADVANCED_TOOLS, - subcategory: SubcategoryId.AUTOMATION + categoryId: ToolCategoryId.ADVANCED_TOOLS, + subcategoryId: SubcategoryId.AUTOMATION }, "auto-split-pages": { icon: split_scene_right, @@ -368,8 +367,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "format", description: t("home.autoSplitPDF.desc", "Automatically split PDF pages based on content detection"), - category: ToolCategory.ADVANCED_TOOLS, - subcategory: SubcategoryId.AUTOMATION + categoryId: ToolCategoryId.ADVANCED_TOOLS, + subcategoryId: SubcategoryId.AUTOMATION }, "auto-split-by-size-count": { icon: content_cut, @@ -377,8 +376,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "format", description: t("home.autoSizeSplitPDF.desc", "Automatically split PDFs by file size or page count"), - category: ToolCategory.ADVANCED_TOOLS, - subcategory: SubcategoryId.AUTOMATION + categoryId: ToolCategoryId.ADVANCED_TOOLS, + subcategoryId: SubcategoryId.AUTOMATION }, @@ -390,8 +389,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "format", description: t("home.adjustContrast.desc", "Adjust colors and contrast of PDF documents"), - category: ToolCategory.ADVANCED_TOOLS, - subcategory: SubcategoryId.ADVANCED_FORMATTING + categoryId: ToolCategoryId.ADVANCED_TOOLS, + subcategoryId: SubcategoryId.ADVANCED_FORMATTING }, "repair": { icon: build, @@ -399,8 +398,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: Repair, view: "format", description: t("home.repair.desc", "Repair corrupted or damaged PDF files"), - category: ToolCategory.ADVANCED_TOOLS, - subcategory: SubcategoryId.ADVANCED_FORMATTING, + categoryId: ToolCategoryId.ADVANCED_TOOLS, + subcategoryId: SubcategoryId.ADVANCED_FORMATTING, maxFiles: -1, endpoints: ["repair"] }, @@ -410,8 +409,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "format", description: t("home.ScannerImageSplit.desc", "Detect and split scanned photos into separate pages"), - category: ToolCategory.ADVANCED_TOOLS, - subcategory: SubcategoryId.ADVANCED_FORMATTING + categoryId: ToolCategoryId.ADVANCED_TOOLS, + subcategoryId: SubcategoryId.ADVANCED_FORMATTING }, "overlay-pdfs": { icon: layers, @@ -419,8 +418,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "format", description: t("home.overlay-pdfs.desc", "Overlay one PDF on top of another"), - category: ToolCategory.ADVANCED_TOOLS, - subcategory: SubcategoryId.ADVANCED_FORMATTING + categoryId: ToolCategoryId.ADVANCED_TOOLS, + subcategoryId: SubcategoryId.ADVANCED_FORMATTING }, "replace-and-invert-color": { icon: format_color_fill, @@ -428,8 +427,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "format", description: t("home.replaceColorPdf.desc", "Replace or invert colors in PDF documents"), - category: ToolCategory.ADVANCED_TOOLS, - subcategory: SubcategoryId.ADVANCED_FORMATTING + categoryId: ToolCategoryId.ADVANCED_TOOLS, + subcategoryId: SubcategoryId.ADVANCED_FORMATTING }, "add-image": { icon: image, @@ -437,8 +436,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "format", description: t("home.addImage.desc", "Add images to PDF documents"), - category: ToolCategory.ADVANCED_TOOLS, - subcategory: SubcategoryId.ADVANCED_FORMATTING + categoryId: ToolCategoryId.ADVANCED_TOOLS, + subcategoryId: SubcategoryId.ADVANCED_FORMATTING }, "edit-table-of-contents": { icon: bookmark_add, @@ -446,8 +445,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "format", description: t("home.editTableOfContents.desc", "Add or edit bookmarks and table of contents in PDF documents"), - category: ToolCategory.ADVANCED_TOOLS, - subcategory: SubcategoryId.ADVANCED_FORMATTING + categoryId: ToolCategoryId.ADVANCED_TOOLS, + subcategoryId: SubcategoryId.ADVANCED_FORMATTING }, "scanner-effect": { icon: scanner, @@ -455,8 +454,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "format", description: t("home.fakeScan.desc", "Create a PDF that looks like it was scanned"), - category: ToolCategory.ADVANCED_TOOLS, - subcategory: SubcategoryId.ADVANCED_FORMATTING + categoryId: ToolCategoryId.ADVANCED_TOOLS, + subcategoryId: SubcategoryId.ADVANCED_FORMATTING }, @@ -468,8 +467,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "extract", description: t("home.showJS.desc", "Extract and display JavaScript code from PDF documents"), - category: ToolCategory.ADVANCED_TOOLS, - subcategory: SubcategoryId.DEVELOPER_TOOLS + categoryId: ToolCategoryId.ADVANCED_TOOLS, + subcategoryId: SubcategoryId.DEVELOPER_TOOLS }, "dev-api": { icon: open_in_new, @@ -477,8 +476,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "external", description: t("home.devApi.desc", "Link to API documentation"), - category: ToolCategory.ADVANCED_TOOLS, - subcategory: SubcategoryId.DEVELOPER_TOOLS, + categoryId: ToolCategoryId.ADVANCED_TOOLS, + subcategoryId: SubcategoryId.DEVELOPER_TOOLS, link: "https://stirlingpdf.io/swagger-ui/5.21.0/index.html" }, "dev-folder-scanning": { @@ -487,8 +486,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "external", description: t("home.devFolderScanning.desc", "Link to automated folder scanning guide"), - category: ToolCategory.ADVANCED_TOOLS, - subcategory: SubcategoryId.DEVELOPER_TOOLS, + categoryId: ToolCategoryId.ADVANCED_TOOLS, + subcategoryId: SubcategoryId.DEVELOPER_TOOLS, link: "https://docs.stirlingpdf.com/Advanced%20Configuration/Folder%20Scanning/" }, "dev-sso-guide": { @@ -497,8 +496,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "external", description: t("home.devSsoGuide.desc", "Link to SSO guide"), - category: ToolCategory.ADVANCED_TOOLS, - subcategory: SubcategoryId.DEVELOPER_TOOLS, + categoryId: ToolCategoryId.ADVANCED_TOOLS, + subcategoryId: SubcategoryId.DEVELOPER_TOOLS, link: "https://docs.stirlingpdf.com/Advanced%20Configuration/Single%20Sign-On%20Configuration", }, "dev-airgapped": { @@ -507,8 +506,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "external", description: t("home.devAirgapped.desc", "Link to air-gapped setup guide"), - category: ToolCategory.ADVANCED_TOOLS, - subcategory: SubcategoryId.DEVELOPER_TOOLS, + categoryId: ToolCategoryId.ADVANCED_TOOLS, + subcategoryId: SubcategoryId.DEVELOPER_TOOLS, link: "https://docs.stirlingpdf.com/Pro/#activation" }, @@ -520,8 +519,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "format", description: t("home.compare.desc", "Compare two PDF documents and highlight differences"), - category: ToolCategory.RECOMMENDED_TOOLS, - subcategory: SubcategoryId.GENERAL + categoryId: ToolCategoryId.RECOMMENDED_TOOLS, + subcategoryId: SubcategoryId.GENERAL }, "compress": { icon: zoom_in_map, @@ -529,8 +528,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: CompressPdfPanel, view: "compress", description: t("home.compress.desc", "Compress PDFs to reduce their file size."), - category: ToolCategory.RECOMMENDED_TOOLS, - subcategory: SubcategoryId.GENERAL, + categoryId: ToolCategoryId.RECOMMENDED_TOOLS, + subcategoryId: SubcategoryId.GENERAL, maxFiles: -1 }, "convert": { @@ -539,8 +538,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: ConvertPanel, view: "convert", description: t("home.convert.desc", "Convert files to and from PDF format"), - category: ToolCategory.RECOMMENDED_TOOLS, - subcategory: SubcategoryId.GENERAL, + categoryId: ToolCategoryId.RECOMMENDED_TOOLS, + subcategoryId: SubcategoryId.GENERAL, maxFiles: -1, endpoints: [ "pdf-to-img", @@ -583,8 +582,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "merge", description: t("home.merge.desc", "Merge multiple PDFs into a single document"), - category: ToolCategory.RECOMMENDED_TOOLS, - subcategory: SubcategoryId.GENERAL, + categoryId: ToolCategoryId.RECOMMENDED_TOOLS, + subcategoryId: SubcategoryId.GENERAL, maxFiles: -1 }, "multi-tool": { @@ -593,8 +592,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "pageEditor", description: t("home.multiTool.desc", "Use multiple tools on a single PDF document"), - category: ToolCategory.RECOMMENDED_TOOLS, - subcategory: SubcategoryId.GENERAL, + categoryId: ToolCategoryId.RECOMMENDED_TOOLS, + subcategoryId: SubcategoryId.GENERAL, maxFiles: -1 }, "ocr": { @@ -603,8 +602,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: OCRPanel, view: "convert", description: t("home.ocr.desc", "Extract text from scanned PDFs using Optical Character Recognition"), - category: ToolCategory.RECOMMENDED_TOOLS, - subcategory: SubcategoryId.GENERAL, + categoryId: ToolCategoryId.RECOMMENDED_TOOLS, + subcategoryId: SubcategoryId.GENERAL, maxFiles: -1 }, "redact": { @@ -613,8 +612,8 @@ export function useFlatToolRegistry(): ToolRegistry { component: null, view: "redact", description: t("home.redact.desc", "Permanently remove sensitive information from PDF documents"), - category: ToolCategory.RECOMMENDED_TOOLS, - subcategory: SubcategoryId.GENERAL + categoryId: ToolCategoryId.RECOMMENDED_TOOLS, + subcategoryId: SubcategoryId.GENERAL }, }; diff --git a/frontend/src/hooks/useSuggestedTools.ts b/frontend/src/hooks/useSuggestedTools.ts index ae6d77035..effbe2397 100644 --- a/frontend/src/hooks/useSuggestedTools.ts +++ b/frontend/src/hooks/useSuggestedTools.ts @@ -9,7 +9,7 @@ import CropIcon from '@mui/icons-material/Crop'; import TextFieldsIcon from '@mui/icons-material/TextFields'; export interface SuggestedTool { - name: string; + id: string /* FIX ME: Should be ToolId */; title: string; icon: React.ComponentType; navigate: () => void; @@ -17,27 +17,27 @@ export interface SuggestedTool { const ALL_SUGGESTED_TOOLS: Omit[] = [ { - name: 'compress', + id: 'compress', title: 'Compress', icon: CompressIcon }, { - name: 'convert', + id: 'convert', title: 'Convert', icon: SwapHorizIcon }, { - name: 'sanitize', + id: 'sanitize', title: 'Sanitize', icon: CleaningServicesIcon }, { - name: 'split', + id: 'split', title: 'Split', icon: CropIcon }, { - name: 'ocr', + id: 'ocr', title: 'OCR', icon: TextFieldsIcon } @@ -48,12 +48,12 @@ export function useSuggestedTools(): SuggestedTool[] { return useMemo(() => { // Filter out the current tool - const filteredTools = ALL_SUGGESTED_TOOLS.filter(tool => tool.name !== selectedToolKey); - + const filteredTools = ALL_SUGGESTED_TOOLS.filter(tool => tool.id !== selectedToolKey); + // Add navigation function to each tool return filteredTools.map(tool => ({ ...tool, - navigate: () => handleToolSelect(tool.name) + navigate: () => handleToolSelect(tool.id) })); }, [selectedToolKey, handleToolSelect]); -} \ No newline at end of file +} diff --git a/frontend/src/hooks/useToolManagement.tsx b/frontend/src/hooks/useToolManagement.tsx index e991677a9..a4c481fcc 100644 --- a/frontend/src/hooks/useToolManagement.tsx +++ b/frontend/src/hooks/useToolManagement.tsx @@ -17,7 +17,7 @@ interface ToolManagementResult { export const useToolManagement = (): ToolManagementResult => { const { t } = useTranslation(); - const [selectedToolKey, setSelectedToolKey] = useState(null); + const [selectedToolKey, setSelectedToolKey] = useState(null); const [toolSelectedFileIds, setToolSelectedFileIds] = useState([]); // Build endpoints list from registry entries with fallback to legacy mapping diff --git a/frontend/src/hooks/useToolSections.ts b/frontend/src/hooks/useToolSections.ts index 4c1a0c05d..41762a8e1 100644 --- a/frontend/src/hooks/useToolSections.ts +++ b/frontend/src/hooks/useToolSections.ts @@ -1,65 +1,87 @@ import { useMemo } from 'react'; -import { SUBCATEGORY_ORDER, ToolCategory, ToolRegistryEntry } from '../data/toolsTaxonomy'; +import { SUBCATEGORY_ORDER, SubcategoryId, ToolCategoryId, ToolRegistryEntry } from '../data/toolsTaxonomy'; import { useTranslation } from 'react-i18next'; +type SubcategoryIdMap = { + [subcategoryId in SubcategoryId]: Array<{ id: string /* FIX ME: Should be ToolId */; tool: ToolRegistryEntry }>; +} + type GroupedTools = { - [category: string]: { - [subcategory: string]: Array<{ id: string; tool: ToolRegistryEntry }>; - }; + [categoryId in ToolCategoryId]: SubcategoryIdMap; }; -export function useToolSections(filteredTools: [string, ToolRegistryEntry][]) { +export interface SubcategoryGroup { + subcategoryId: SubcategoryId; + tools: { + id: string /* FIX ME: Should be ToolId */; + tool: ToolRegistryEntry; + }[]; +}; + +export type ToolSectionKey = 'quick' | 'all'; + +export interface ToolSection { + key: ToolSectionKey; + title: string; + subcategories: SubcategoryGroup[]; +}; + +export function useToolSections(filteredTools: [string /* FIX ME: Should be ToolId */, ToolRegistryEntry][]) { const { t } = useTranslation(); const groupedTools = useMemo(() => { - const grouped: GroupedTools = {}; + const grouped = {} as GroupedTools; filteredTools.forEach(([id, tool]) => { - const category = tool.category; - const subcategory = tool.subcategory; - if (!grouped[category]) grouped[category] = {}; - if (!grouped[category][subcategory]) grouped[category][subcategory] = []; - grouped[category][subcategory].push({ id, tool }); + const categoryId = tool.categoryId; + const subcategoryId = tool.subcategoryId; + if (!grouped[categoryId]) grouped[categoryId] = {} as SubcategoryIdMap; + if (!grouped[categoryId][subcategoryId]) grouped[categoryId][subcategoryId] = []; + grouped[categoryId][subcategoryId].push({ id, tool }); }); return grouped; }, [filteredTools]); - const sections = useMemo(() => { - const getOrderIndex = (name: string) => { - const idx = SUBCATEGORY_ORDER.indexOf(name as any); + const sections: ToolSection[] = useMemo(() => { + const getOrderIndex = (id: SubcategoryId) => { + const idx = SUBCATEGORY_ORDER.indexOf(id); return idx === -1 ? Number.MAX_SAFE_INTEGER : idx; }; - const quick: Record> = {}; - const all: Record> = {}; + const quick = {} as SubcategoryIdMap; + const all = {} as SubcategoryIdMap; - Object.entries(groupedTools).forEach(([origCat, subs]) => { - const upperCat = origCat.toUpperCase(); + Object.entries(groupedTools).forEach(([c, subs]) => { + const categoryId = c as ToolCategoryId; - Object.entries(subs).forEach(([sub, tools]) => { - if (!all[sub]) all[sub] = []; - all[sub].push(...tools); + Object.entries(subs).forEach(([s, tools]) => { + const subcategoryId = s as SubcategoryId; + if (!all[subcategoryId]) all[subcategoryId] = []; + all[subcategoryId].push(...tools); }); - if (upperCat === ToolCategory.RECOMMENDED_TOOLS.toUpperCase()) { - Object.entries(subs).forEach(([sub, tools]) => { - if (!quick[sub]) quick[sub] = []; - quick[sub].push(...tools); + if (categoryId === ToolCategoryId.RECOMMENDED_TOOLS) { + Object.entries(subs).forEach(([s, tools]) => { + const subcategoryId = s as SubcategoryId; + if (!quick[subcategoryId]) quick[subcategoryId] = []; + quick[subcategoryId].push(...tools); }); } }); - const sortSubs = (obj: Record>) => + const sortSubs = (obj: SubcategoryIdMap) => Object.entries(obj) .sort(([a], [b]) => { - const ai = getOrderIndex(a); - const bi = getOrderIndex(b); + const aId = a as SubcategoryId; + const bId = b as SubcategoryId; + const ai = getOrderIndex(aId); + const bi = getOrderIndex(bId); if (ai !== bi) return ai - bi; - return a.localeCompare(b); + return aId.localeCompare(bId); }) - .map(([subcategory, tools]) => ({ subcategory, tools })); + .map(([subcategoryId, tools]) => ({ subcategoryId, tools } as SubcategoryGroup)); - const built = [ + const built: ToolSection[] = [ { key: 'quick', title: t('toolPicker.quickAccess', 'QUICK ACCESS'), subcategories: sortSubs(quick) }, { key: 'all', title: t('toolPicker.allTools', 'ALL TOOLS'), subcategories: sortSubs(all) } ]; @@ -67,19 +89,20 @@ export function useToolSections(filteredTools: [string, ToolRegistryEntry][]) { return built.filter(section => section.subcategories.some(sc => sc.tools.length > 0)); }, [groupedTools]); - const searchGroups = useMemo(() => { - const subMap: Record> = {}; - const seen = new Set(); + const searchGroups: SubcategoryGroup[] = useMemo(() => { + const subMap = {} as SubcategoryIdMap; + const seen = new Set(); filteredTools.forEach(([id, tool]) => { - if (seen.has(id)) return; - seen.add(id); - const sub = tool.subcategory; + const toolId = id as string /* FIX ME: Should be ToolId */; + if (seen.has(toolId)) return; + seen.add(toolId); + const sub = tool.subcategoryId; if (!subMap[sub]) subMap[sub] = []; - subMap[sub].push({ id, tool }); + subMap[sub].push({ id: toolId, tool }); }); return Object.entries(subMap) .sort(([a], [b]) => a.localeCompare(b)) - .map(([subcategory, tools]) => ({ subcategory, tools })); + .map(([subcategoryId, tools]) => ({ subcategoryId, tools } as SubcategoryGroup)); }, [filteredTools]); return { sections, searchGroups };