mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-03 21:03:08 +00:00
feat: add accept all functionality in ideation view
- Introduced a new "Accept All" button in the IdeationHeader component, allowing users to accept all filtered suggestions at once. - Implemented state management for accept all readiness and count in the IdeationView component. - Enhanced the IdeationDashboard to notify the parent component about the readiness of the accept all feature. - Added logic to handle the acceptance of all suggestions, including success and failure notifications. - Updated UI components to reflect the new functionality and improve user experience.
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
* First page users see - shows all ideas ready for accept/reject
|
* 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 { Loader2, AlertCircle, Plus, X, Sparkles, Lightbulb } from 'lucide-react';
|
||||||
import { Card, CardContent } from '@/components/ui/card';
|
import { Card, CardContent } from '@/components/ui/card';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
@@ -17,6 +17,7 @@ import type { AnalysisSuggestion } from '@automaker/types';
|
|||||||
|
|
||||||
interface IdeationDashboardProps {
|
interface IdeationDashboardProps {
|
||||||
onGenerateIdeas: () => void;
|
onGenerateIdeas: () => void;
|
||||||
|
onAcceptAllReady?: (isReady: boolean, count: number, handler: () => Promise<void>) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SuggestionCard({
|
function SuggestionCard({
|
||||||
@@ -37,14 +38,16 @@ function SuggestionCard({
|
|||||||
<CardContent className="p-4">
|
<CardContent className="p-4">
|
||||||
<div className="flex items-start gap-4">
|
<div className="flex items-start gap-4">
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div className="flex items-center gap-2 mb-1">
|
<div className="flex items-start gap-2 mb-1">
|
||||||
<h4 className="font-medium">{suggestion.title}</h4>
|
<h4 className="font-medium shrink-0">{suggestion.title}</h4>
|
||||||
<Badge variant="outline" className="text-xs">
|
<div className="flex items-center gap-2 flex-wrap">
|
||||||
{suggestion.priority}
|
<Badge variant="outline" className="text-xs whitespace-nowrap">
|
||||||
</Badge>
|
{suggestion.priority}
|
||||||
<Badge variant="secondary" className="text-xs">
|
</Badge>
|
||||||
{job.prompt.title}
|
<Badge variant="secondary" className="text-xs whitespace-nowrap">
|
||||||
</Badge>
|
{job.prompt.title}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-muted-foreground">{suggestion.description}</p>
|
<p className="text-sm text-muted-foreground">{suggestion.description}</p>
|
||||||
{suggestion.rationale && (
|
{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 currentProject = useAppStore((s) => s.currentProject);
|
||||||
const generationJobs = useIdeationStore((s) => s.generationJobs);
|
const generationJobs = useIdeationStore((s) => s.generationJobs);
|
||||||
const removeSuggestionFromJob = useIdeationStore((s) => s.removeSuggestionFromJob);
|
const removeSuggestionFromJob = useIdeationStore((s) => s.removeSuggestionFromJob);
|
||||||
const [addingId, setAddingId] = useState<string | null>(null);
|
const [addingId, setAddingId] = useState<string | null>(null);
|
||||||
|
const [isAcceptingAll, setIsAcceptingAll] = useState(false);
|
||||||
const [selectedTags, setSelectedTags] = useState<Set<string>>(new Set());
|
const [selectedTags, setSelectedTags] = useState<Set<string>>(new Set());
|
||||||
|
|
||||||
// Get jobs for current project only (memoized to prevent unnecessary re-renders)
|
// 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');
|
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;
|
const isEmpty = allSuggestions.length === 0 && activeJobs.length === 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* Dashboard-first design with Generate Ideas flow
|
* Dashboard-first design with Generate Ideas flow
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useCallback } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
import { useIdeationStore } from '@/store/ideation-store';
|
import { useIdeationStore } from '@/store/ideation-store';
|
||||||
import { useAppStore } from '@/store/app-store';
|
import { useAppStore } from '@/store/app-store';
|
||||||
import { PromptCategoryGrid } from './components/prompt-category-grid';
|
import { PromptCategoryGrid } from './components/prompt-category-grid';
|
||||||
@@ -11,7 +11,7 @@ import { PromptList } from './components/prompt-list';
|
|||||||
import { IdeationDashboard } from './components/ideation-dashboard';
|
import { IdeationDashboard } from './components/ideation-dashboard';
|
||||||
import { useGuidedPrompts } from '@/hooks/use-guided-prompts';
|
import { useGuidedPrompts } from '@/hooks/use-guided-prompts';
|
||||||
import { Button } from '@/components/ui/button';
|
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 { IdeaCategory } from '@automaker/types';
|
||||||
import type { IdeationMode } from '@/store/ideation-store';
|
import type { IdeationMode } from '@/store/ideation-store';
|
||||||
|
|
||||||
@@ -67,12 +67,20 @@ function IdeationHeader({
|
|||||||
onNavigate,
|
onNavigate,
|
||||||
onGenerateIdeas,
|
onGenerateIdeas,
|
||||||
onBack,
|
onBack,
|
||||||
|
acceptAllReady,
|
||||||
|
acceptAllCount,
|
||||||
|
onAcceptAll,
|
||||||
|
isAcceptingAll,
|
||||||
}: {
|
}: {
|
||||||
currentMode: IdeationMode;
|
currentMode: IdeationMode;
|
||||||
selectedCategory: IdeaCategory | null;
|
selectedCategory: IdeaCategory | null;
|
||||||
onNavigate: (mode: IdeationMode, category?: IdeaCategory | null) => void;
|
onNavigate: (mode: IdeationMode, category?: IdeaCategory | null) => void;
|
||||||
onGenerateIdeas: () => void;
|
onGenerateIdeas: () => void;
|
||||||
onBack: () => void;
|
onBack: () => void;
|
||||||
|
acceptAllReady: boolean;
|
||||||
|
acceptAllCount: number;
|
||||||
|
onAcceptAll: () => void;
|
||||||
|
isAcceptingAll: boolean;
|
||||||
}) {
|
}) {
|
||||||
const { getCategoryById } = useGuidedPrompts();
|
const { getCategoryById } = useGuidedPrompts();
|
||||||
const showBackButton = currentMode === 'prompts';
|
const showBackButton = currentMode === 'prompts';
|
||||||
@@ -120,6 +128,21 @@ function IdeationHeader({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex gap-2 items-center">
|
||||||
|
{currentMode === 'dashboard' && acceptAllReady && (
|
||||||
|
<Button
|
||||||
|
onClick={onAcceptAll}
|
||||||
|
variant="outline"
|
||||||
|
className="gap-2"
|
||||||
|
disabled={isAcceptingAll}
|
||||||
|
>
|
||||||
|
{isAcceptingAll ? (
|
||||||
|
<Loader2 className="w-4 h-4 animate-spin" />
|
||||||
|
) : (
|
||||||
|
<CheckCheck className="w-4 h-4" />
|
||||||
|
)}
|
||||||
|
Accept All ({acceptAllCount})
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
<Button onClick={onGenerateIdeas} className="gap-2">
|
<Button onClick={onGenerateIdeas} className="gap-2">
|
||||||
<Lightbulb className="w-4 h-4" />
|
<Lightbulb className="w-4 h-4" />
|
||||||
Generate Ideas
|
Generate Ideas
|
||||||
@@ -133,6 +156,32 @@ export function IdeationView() {
|
|||||||
const currentProject = useAppStore((s) => s.currentProject);
|
const currentProject = useAppStore((s) => s.currentProject);
|
||||||
const { currentMode, selectedCategory, setMode, setCategory } = useIdeationStore();
|
const { currentMode, selectedCategory, setMode, setCategory } = useIdeationStore();
|
||||||
|
|
||||||
|
// Accept all state
|
||||||
|
const [acceptAllReady, setAcceptAllReady] = useState(false);
|
||||||
|
const [acceptAllCount, setAcceptAllCount] = useState(0);
|
||||||
|
const [acceptAllHandler, setAcceptAllHandler] = useState<(() => Promise<void>) | null>(null);
|
||||||
|
const [isAcceptingAll, setIsAcceptingAll] = useState(false);
|
||||||
|
|
||||||
|
const handleAcceptAllReady = useCallback(
|
||||||
|
(isReady: boolean, count: number, handler: () => Promise<void>) => {
|
||||||
|
setAcceptAllReady(isReady);
|
||||||
|
setAcceptAllCount(count);
|
||||||
|
setAcceptAllHandler(() => handler);
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleAcceptAll = useCallback(async () => {
|
||||||
|
if (acceptAllHandler) {
|
||||||
|
setIsAcceptingAll(true);
|
||||||
|
try {
|
||||||
|
await acceptAllHandler();
|
||||||
|
} finally {
|
||||||
|
setIsAcceptingAll(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [acceptAllHandler]);
|
||||||
|
|
||||||
const handleNavigate = useCallback(
|
const handleNavigate = useCallback(
|
||||||
(mode: IdeationMode, category?: IdeaCategory | null) => {
|
(mode: IdeationMode, category?: IdeaCategory | null) => {
|
||||||
setMode(mode);
|
setMode(mode);
|
||||||
@@ -192,10 +241,19 @@ export function IdeationView() {
|
|||||||
onNavigate={handleNavigate}
|
onNavigate={handleNavigate}
|
||||||
onGenerateIdeas={handleGenerateIdeas}
|
onGenerateIdeas={handleGenerateIdeas}
|
||||||
onBack={handleBackFromPrompts}
|
onBack={handleBackFromPrompts}
|
||||||
|
acceptAllReady={acceptAllReady}
|
||||||
|
acceptAllCount={acceptAllCount}
|
||||||
|
onAcceptAll={handleAcceptAll}
|
||||||
|
isAcceptingAll={isAcceptingAll}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Dashboard - main view */}
|
{/* Dashboard - main view */}
|
||||||
{currentMode === 'dashboard' && <IdeationDashboard onGenerateIdeas={handleGenerateIdeas} />}
|
{currentMode === 'dashboard' && (
|
||||||
|
<IdeationDashboard
|
||||||
|
onGenerateIdeas={handleGenerateIdeas}
|
||||||
|
onAcceptAllReady={handleAcceptAllReady}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Prompts - category selection */}
|
{/* Prompts - category selection */}
|
||||||
{currentMode === 'prompts' && !selectedCategory && (
|
{currentMode === 'prompts' && !selectedCategory && (
|
||||||
|
|||||||
Reference in New Issue
Block a user