Sfoglia il codice sorgente

chore(dashboard): Update implementation plan

David Höck 5 mesi fa
parent
commit
0bd3b8da44
1 ha cambiato i file con 178 aggiunte e 0 eliminazioni
  1. 178 0
      GLOBAL_SEARCH_IMPLEMENTATION_PLAN.md

+ 178 - 0
GLOBAL_SEARCH_IMPLEMENTATION_PLAN.md

@@ -846,6 +846,55 @@ export class WebsiteContentService {
 
 ## Frontend Implementation
 
+### Integration with App Layout
+
+Based on the current dashboard structure, the command palette should be integrated into the main app layout to be available globally. Here's how to modify the existing `app-layout.tsx`:
+
+```typescript
+// packages/dashboard/src/lib/components/layout/app-layout.tsx
+import { CommandPalette } from '@/vdb/components/search/command-palette.js';
+import { SearchProvider } from '@/vdb/providers/search-provider.js';
+import { useKeyboardShortcuts } from '@/vdb/hooks/use-keyboard-shortcuts.js';
+
+export function AppLayout() {
+    const { settings } = useUserSettings();
+    const { isCommandPaletteOpen, setIsCommandPaletteOpen } = useKeyboardShortcuts();
+    
+    return (
+        <SearchProvider>
+            <SidebarProvider>
+                <AppSidebar />
+                <SidebarInset>
+                    <div className="container mx-auto">
+                        <header className="border-b border-border flex h-16 shrink-0 items-center gap-2 transition-[width,height] ease-linear group-has-[[data-collapsible=icon]]/sidebar-wrapper:h-12">
+                            <div className="flex items-center justify-between gap-2 px-4 w-full">
+                                <div className="flex items-center justify-start gap-2">
+                                    <SidebarTrigger className="-ml-1" />
+                                    <Separator orientation="vertical" className="mr-2 h-4" />
+                                    <GeneratedBreadcrumbs />
+                                </div>
+                                <div className="flex items-center justify-end gap-2">
+                                    {settings.devMode && <DevModeIndicator />}
+                                    <Alerts />
+                                </div>
+                            </div>
+                        </header>
+                        <Outlet />
+                    </div>
+                </SidebarInset>
+                <PrereleasePopup />
+                
+                {/* Global Command Palette */}
+                <CommandPalette 
+                    isOpen={isCommandPaletteOpen}
+                    onOpenChange={setIsCommandPaletteOpen}
+                />
+            </SidebarProvider>
+        </SearchProvider>
+    );
+}
+```
+
 ### Search Component Structure
 
 ```
@@ -950,6 +999,135 @@ export const useQuickActions = () => {
         return actions;
     };
 
+    // Register custom actions (from defineDashboardExtensions)
+    const registerAction = useCallback((action: QuickAction) => {
+        if (action.isContextAware) {
+            setContextActions(prev => [...prev, action]);
+        } else {
+            setGlobalActions(prev => [...prev, action]);
+        }
+    }, []);
+
+    // Get all available actions for current context
+    const getAvailableActions = useMemo(() => {
+        const currentContextActions = getContextActions(
+            location.pathname,
+            // Extract entity type from route
+            location.pathname.split('/')[1]
+        );
+
+        return [
+            ...builtInGlobalActions,
+            ...globalActions,
+            ...currentContextActions,
+            ...contextActions,
+        ];
+    }, [location.pathname, globalActions, contextActions]);
+
+    return {
+        actions: getAvailableActions,
+        registerAction,
+    };
+};
+```
+
+### Search Hooks and Context
+
+```typescript
+// packages/dashboard/src/lib/providers/search-provider.tsx
+import { createContext, useContext, useState, ReactNode } from 'react';
+
+interface SearchContextType {
+    isCommandPaletteOpen: boolean;
+    setIsCommandPaletteOpen: (open: boolean) => void;
+    searchQuery: string;
+    setSearchQuery: (query: string) => void;
+    searchResults: SearchResult[];
+    setSearchResults: (results: SearchResult[]) => void;
+    isSearching: boolean;
+    setIsSearching: (searching: boolean) => void;
+}
+
+const SearchContext = createContext<SearchContextType | undefined>(undefined);
+
+export const SearchProvider = ({ children }: { children: ReactNode }) => {
+    const [isCommandPaletteOpen, setIsCommandPaletteOpen] = useState(false);
+    const [searchQuery, setSearchQuery] = useState('');
+    const [searchResults, setSearchResults] = useState<SearchResult[]>([]);
+    const [isSearching, setIsSearching] = useState(false);
+
+    return (
+        <SearchContext.Provider 
+            value={{
+                isCommandPaletteOpen,
+                setIsCommandPaletteOpen,
+                searchQuery,
+                setSearchQuery,
+                searchResults,
+                setSearchResults,
+                isSearching,
+                setIsSearching,
+            }}
+        >
+            {children}
+        </SearchContext.Provider>
+    );
+};
+
+export const useSearchContext = () => {
+    const context = useContext(SearchContext);
+    if (!context) {
+        throw new Error('useSearchContext must be used within SearchProvider');
+    }
+    return context;
+};
+```
+
+```typescript
+// packages/dashboard/src/lib/hooks/use-global-search.ts
+import { useQuery } from '@apollo/client';
+import { useDebounce } from '@/vdb/hooks/use-debounce.js';
+import { useSearchContext } from '@/vdb/providers/search-provider.js';
+import { globalSearchDocument } from '@/vdb/graphql/search.js';
+
+export const useGlobalSearch = () => {
+    const { searchQuery, setSearchResults, setIsSearching } = useSearchContext();
+    const debouncedQuery = useDebounce(searchQuery, 300);
+
+    const { data, loading, error } = useQuery(globalSearchDocument, {
+        variables: {
+            input: {
+                query: debouncedQuery,
+                types: [], // All types by default
+                limit: 20,
+            }
+        },
+        skip: !debouncedQuery || debouncedQuery.length < 2,
+        onCompleted: (data) => {
+            setSearchResults(data.globalSearch.items);
+            setIsSearching(false);
+        },
+        onError: () => {
+            setSearchResults([]);
+            setIsSearching(false);
+        }
+    });
+
+    // Update loading state
+    React.useEffect(() => {
+        setIsSearching(loading);
+    }, [loading, setIsSearching]);
+
+    return {
+        results: data?.globalSearch.items || [],
+        loading,
+        error,
+        totalCount: data?.globalSearch.totalItems || 0,
+    };
+};
+```
+    };
+
     // Register custom actions from extensions
     const registerCustomAction = (action: QuickAction) => {
         if (action.isContextAware) {