diff --git a/frontend/src/components/common/SearchModal.tsx b/frontend/src/components/common/SearchModal.tsx index 63ee450c..787ef55d 100644 --- a/frontend/src/components/common/SearchModal.tsx +++ b/frontend/src/components/common/SearchModal.tsx @@ -1,5 +1,6 @@ /** * Search Modal - Global search modal triggered by icon or Cmd+K + * Enhanced with filters and recent searches */ import { useState, useEffect, useRef } from 'react'; import { useNavigate } from 'react-router-dom'; @@ -14,45 +15,85 @@ interface SearchModalProps { interface SearchResult { title: string; path: string; - type: 'page' | 'action'; + type: 'workflow' | 'setup' | 'account' | 'help'; + category: string; icon?: string; } +type FilterType = 'all' | 'workflow' | 'setup' | 'account' | 'help'; + +const RECENT_SEARCHES_KEY = 'igny8_recent_searches'; +const MAX_RECENT_SEARCHES = 5; + const SEARCH_ITEMS: SearchResult[] = [ // Workflow - { title: 'Keywords', path: '/planner/keywords', type: 'page' }, - { title: 'Clusters', path: '/planner/clusters', type: 'page' }, - { title: 'Ideas', path: '/planner/ideas', type: 'page' }, - { title: 'Queue', path: '/writer/tasks', type: 'page' }, - { title: 'Drafts', path: '/writer/content', type: 'page' }, - { title: 'Images', path: '/writer/images', type: 'page' }, - { title: 'Review', path: '/writer/review', type: 'page' }, - { title: 'Approved', path: '/writer/approved', type: 'page' }, + { title: 'Keywords', path: '/planner/keywords', type: 'workflow', category: 'Planner' }, + { title: 'Clusters', path: '/planner/clusters', type: 'workflow', category: 'Planner' }, + { title: 'Ideas', path: '/planner/ideas', type: 'workflow', category: 'Planner' }, + { title: 'Queue', path: '/writer/tasks', type: 'workflow', category: 'Writer' }, + { title: 'Drafts', path: '/writer/content', type: 'workflow', category: 'Writer' }, + { title: 'Images', path: '/writer/images', type: 'workflow', category: 'Writer' }, + { title: 'Review', path: '/writer/review', type: 'workflow', category: 'Writer' }, + { title: 'Approved', path: '/writer/approved', type: 'workflow', category: 'Writer' }, + { title: 'Automation', path: '/automation', type: 'workflow', category: 'Automation' }, + { title: 'Content Calendar', path: '/publisher/content-calendar', type: 'workflow', category: 'Publisher' }, // Setup - { title: 'Sites', path: '/sites', type: 'page' }, - { title: 'Add Keywords', path: '/add-keywords', type: 'page' }, - { title: 'Content Settings', path: '/account/content-settings', type: 'page' }, - { title: 'Prompts', path: '/thinker/prompts', type: 'page' }, - { title: 'Author Profiles', path: '/thinker/author-profiles', type: 'page' }, + { title: 'Sites', path: '/sites', type: 'setup', category: 'Sites' }, + { title: 'Add Keywords', path: '/setup/add-keywords', type: 'setup', category: 'Setup' }, + { title: 'Content Settings', path: '/account/content-settings', type: 'setup', category: 'Settings' }, + { title: 'Prompts', path: '/thinker/prompts', type: 'setup', category: 'AI' }, + { title: 'Author Profiles', path: '/thinker/author-profiles', type: 'setup', category: 'AI' }, // Account - { title: 'Account Settings', path: '/account/settings', type: 'page' }, - { title: 'Plans & Billing', path: '/account/plans', type: 'page' }, - { title: 'Usage Analytics', path: '/account/usage', type: 'page' }, + { title: 'Account Settings', path: '/account/settings', type: 'account', category: 'Account' }, + { title: 'Plans & Billing', path: '/account/plans', type: 'account', category: 'Account' }, + { title: 'Usage Analytics', path: '/account/usage', type: 'account', category: 'Account' }, + { title: 'Team Management', path: '/account/settings/team', type: 'account', category: 'Account' }, + { title: 'Notifications', path: '/account/notifications', type: 'account', category: 'Account' }, // Help - { title: 'Help & Support', path: '/help', type: 'page' }, + { title: 'Help & Support', path: '/help', type: 'help', category: 'Help' }, ]; export default function SearchModal({ isOpen, onClose }: SearchModalProps) { const [query, setQuery] = useState(''); const [selectedIndex, setSelectedIndex] = useState(0); + const [activeFilter, setActiveFilter] = useState('all'); + const [recentSearches, setRecentSearches] = useState([]); const inputRef = useRef(null); const navigate = useNavigate(); + // Load recent searches from localStorage + useEffect(() => { + const stored = localStorage.getItem(RECENT_SEARCHES_KEY); + if (stored) { + try { + setRecentSearches(JSON.parse(stored)); + } catch { + setRecentSearches([]); + } + } + }, []); + + // Save recent search + const addRecentSearch = (path: string) => { + const updated = [path, ...recentSearches.filter(p => p !== path)].slice(0, MAX_RECENT_SEARCHES); + setRecentSearches(updated); + localStorage.setItem(RECENT_SEARCHES_KEY, JSON.stringify(updated)); + }; + + const getRecentSearchResults = (): SearchResult[] => { + return recentSearches + .map(path => SEARCH_ITEMS.find(item => item.path === path)) + .filter((item): item is SearchResult => item !== undefined); + }; + const filteredResults = query.length > 0 - ? SEARCH_ITEMS.filter(item => - item.title.toLowerCase().includes(query.toLowerCase()) - ) - : SEARCH_ITEMS.slice(0, 8); + ? SEARCH_ITEMS.filter(item => { + const matchesQuery = item.title.toLowerCase().includes(query.toLowerCase()) || + item.category.toLowerCase().includes(query.toLowerCase()); + const matchesFilter = activeFilter === 'all' || item.type === activeFilter; + return matchesQuery && matchesFilter; + }) + : (activeFilter === 'all' ? getRecentSearchResults() : SEARCH_ITEMS.filter(item => item.type === activeFilter)); useEffect(() => { if (isOpen) { @@ -80,13 +121,23 @@ export default function SearchModal({ isOpen, onClose }: SearchModalProps) { }; const handleSelect = (result: SearchResult) => { + addRecentSearch(result.path); navigate(result.path); onClose(); }; + const filterOptions: { value: FilterType; label: string }[] = [ + { value: 'all', label: 'All' }, + { value: 'workflow', label: 'Workflow' }, + { value: 'setup', label: 'Setup' }, + { value: 'account', label: 'Account' }, + { value: 'help', label: 'Help' }, + ]; + return ( - +
+ {/* Search Input */}
@@ -107,11 +158,40 @@ export default function SearchModal({ isOpen, onClose }: SearchModalProps) { ESC to close
+ + {/* Filters */} +
+ {filterOptions.map((filter) => ( + + ))} +
+ + {/* Recent Searches Header (only when showing recent) */} + {query.length === 0 && activeFilter === 'all' && recentSearches.length > 0 && ( +
+ Recent Searches +
+ )} + {/* Results */}
{filteredResults.length === 0 ? (
- No results found for "{query}" + {query.length > 0 + ? `No results found for "${query}"` + : 'No recent searches'}
) : ( filteredResults.map((result, index) => ( @@ -126,10 +206,13 @@ export default function SearchModal({ isOpen, onClose }: SearchModalProps) { : 'hover:bg-gray-50 dark:hover:bg-gray-800 text-gray-700 dark:text-gray-300' }`} > - + - {result.title} +
+
{result.title}
+
{result.category}
+
)) )}