diff --git a/apps/ui/src/components/views/ideation-view/components/ideation-dashboard.tsx b/apps/ui/src/components/views/ideation-view/components/ideation-dashboard.tsx index 953cb8c6..71234fe7 100644 --- a/apps/ui/src/components/views/ideation-view/components/ideation-dashboard.tsx +++ b/apps/ui/src/components/views/ideation-view/components/ideation-dashboard.tsx @@ -3,7 +3,7 @@ * First page users see - shows all ideas ready for accept/reject */ -import { useState, useMemo } from 'react'; +import { useState, useMemo, useEffect, useCallback } from 'react'; import { Loader2, AlertCircle, Plus, X, Sparkles, Lightbulb } from 'lucide-react'; import { Card, CardContent } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; @@ -17,6 +17,7 @@ import type { AnalysisSuggestion } from '@automaker/types'; interface IdeationDashboardProps { onGenerateIdeas: () => void; + onAcceptAllReady?: (isReady: boolean, count: number, handler: () => Promise) => void; } function SuggestionCard({ @@ -37,14 +38,16 @@ function SuggestionCard({
-
-

{suggestion.title}

- - {suggestion.priority} - - - {job.prompt.title} - +
+

{suggestion.title}

+
+ + {suggestion.priority} + + + {job.prompt.title} + +

{suggestion.description}

{suggestion.rationale && ( @@ -166,11 +169,12 @@ function TagFilter({ ); } -export function IdeationDashboard({ onGenerateIdeas }: IdeationDashboardProps) { +export function IdeationDashboard({ onGenerateIdeas, onAcceptAllReady }: IdeationDashboardProps) { const currentProject = useAppStore((s) => s.currentProject); const generationJobs = useIdeationStore((s) => s.generationJobs); const removeSuggestionFromJob = useIdeationStore((s) => s.removeSuggestionFromJob); const [addingId, setAddingId] = useState(null); + const [isAcceptingAll, setIsAcceptingAll] = useState(false); const [selectedTags, setSelectedTags] = useState>(new Set()); // Get jobs for current project only (memoized to prevent unnecessary re-renders) @@ -270,6 +274,54 @@ export function IdeationDashboard({ onGenerateIdeas }: IdeationDashboardProps) { toast.info('Idea removed'); }; + // Accept all filtered suggestions + const handleAcceptAll = useCallback(async () => { + if (!currentProject?.path || filteredSuggestions.length === 0) { + return; + } + + setIsAcceptingAll(true); + const api = getElectronAPI(); + let successCount = 0; + let failCount = 0; + + // Process all filtered suggestions + for (const { suggestion, job } of filteredSuggestions) { + try { + const result = await api.ideation?.addSuggestionToBoard(currentProject.path, suggestion); + if (result?.success) { + removeSuggestionFromJob(job.id, suggestion.id); + successCount++; + } else { + failCount++; + } + } catch (error) { + console.error('Failed to add suggestion to board:', error); + failCount++; + } + } + + setIsAcceptingAll(false); + + if (successCount > 0 && failCount === 0) { + toast.success(`Added ${successCount} idea${successCount > 1 ? 's' : ''} to board`); + } else if (successCount > 0 && failCount > 0) { + toast.warning( + `Added ${successCount} idea${successCount > 1 ? 's' : ''}, ${failCount} failed` + ); + } else { + toast.error('Failed to add ideas to board'); + } + }, [currentProject?.path, filteredSuggestions, removeSuggestionFromJob]); + + // Notify parent about accept all readiness + useEffect(() => { + if (onAcceptAllReady) { + const isReady = filteredSuggestions.length > 0 && !isAcceptingAll && !addingId; + onAcceptAllReady(isReady, filteredSuggestions.length, handleAcceptAll); + } + }, [filteredSuggestions.length, isAcceptingAll, addingId, handleAcceptAll, onAcceptAllReady]); + const isEmpty = allSuggestions.length === 0 && activeJobs.length === 0; return ( diff --git a/apps/ui/src/components/views/ideation-view/index.tsx b/apps/ui/src/components/views/ideation-view/index.tsx index fd4ad245..ce741c93 100644 --- a/apps/ui/src/components/views/ideation-view/index.tsx +++ b/apps/ui/src/components/views/ideation-view/index.tsx @@ -3,7 +3,7 @@ * Dashboard-first design with Generate Ideas flow */ -import { useCallback } from 'react'; +import { useCallback, useState } from 'react'; import { useIdeationStore } from '@/store/ideation-store'; import { useAppStore } from '@/store/app-store'; import { PromptCategoryGrid } from './components/prompt-category-grid'; @@ -11,7 +11,7 @@ import { PromptList } from './components/prompt-list'; import { IdeationDashboard } from './components/ideation-dashboard'; import { useGuidedPrompts } from '@/hooks/use-guided-prompts'; import { Button } from '@/components/ui/button'; -import { ArrowLeft, ChevronRight, Lightbulb } from 'lucide-react'; +import { ArrowLeft, ChevronRight, Lightbulb, CheckCheck, Loader2 } from 'lucide-react'; import type { IdeaCategory } from '@automaker/types'; import type { IdeationMode } from '@/store/ideation-store'; @@ -67,12 +67,20 @@ function IdeationHeader({ onNavigate, onGenerateIdeas, onBack, + acceptAllReady, + acceptAllCount, + onAcceptAll, + isAcceptingAll, }: { currentMode: IdeationMode; selectedCategory: IdeaCategory | null; onNavigate: (mode: IdeationMode, category?: IdeaCategory | null) => void; onGenerateIdeas: () => void; onBack: () => void; + acceptAllReady: boolean; + acceptAllCount: number; + onAcceptAll: () => void; + isAcceptingAll: boolean; }) { const { getCategoryById } = useGuidedPrompts(); const showBackButton = currentMode === 'prompts'; @@ -120,6 +128,21 @@ function IdeationHeader({
+ {currentMode === 'dashboard' && acceptAllReady && ( + + )}