Merge pull request #506 from AutoMaker-Org/feature/v0.12.0rc-1768509904121-pjft

feat: add discard all functionality to ideation view
This commit is contained in:
Shirone
2026-01-15 21:39:54 +00:00
committed by GitHub
2 changed files with 96 additions and 15 deletions

View File

@@ -4,10 +4,11 @@
*/
import { useState, useMemo, useEffect, useCallback } from 'react';
import { Loader2, AlertCircle, Plus, X, Sparkles, Lightbulb } from 'lucide-react';
import { Loader2, AlertCircle, Plus, X, Sparkles, Lightbulb, Trash2 } from 'lucide-react';
import { Card, CardContent } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { ConfirmDialog } from '@/components/ui/confirm-dialog';
import { useIdeationStore, type GenerationJob } from '@/store/ideation-store';
import { useAppStore } from '@/store/app-store';
import { getElectronAPI } from '@/lib/electron';
@@ -15,9 +16,13 @@ import { toast } from 'sonner';
import { cn } from '@/lib/utils';
import type { AnalysisSuggestion } from '@automaker/types';
// Helper for consistent pluralization of "idea/ideas"
const pluralizeIdea = (count: number) => `idea${count !== 1 ? 's' : ''}`;
interface IdeationDashboardProps {
onGenerateIdeas: () => void;
onAcceptAllReady?: (isReady: boolean, count: number, handler: () => Promise<void>) => void;
onDiscardAllReady?: (isReady: boolean, count: number, handler: () => void) => void;
}
function SuggestionCard({
@@ -169,13 +174,18 @@ function TagFilter({
);
}
export function IdeationDashboard({ onGenerateIdeas, onAcceptAllReady }: IdeationDashboardProps) {
export function IdeationDashboard({
onGenerateIdeas,
onAcceptAllReady,
onDiscardAllReady,
}: IdeationDashboardProps) {
const currentProject = useAppStore((s) => s.currentProject);
const generationJobs = useIdeationStore((s) => s.generationJobs);
const removeSuggestionFromJob = useIdeationStore((s) => s.removeSuggestionFromJob);
const [addingId, setAddingId] = useState<string | null>(null);
const [isAcceptingAll, setIsAcceptingAll] = useState(false);
const [selectedTags, setSelectedTags] = useState<Set<string>>(new Set());
const [showDiscardConfirm, setShowDiscardConfirm] = useState(false);
// Get jobs for current project only (memoized to prevent unnecessary re-renders)
const projectJobs = useMemo(
@@ -304,23 +314,40 @@ export function IdeationDashboard({ onGenerateIdeas, onAcceptAllReady }: Ideatio
setIsAcceptingAll(false);
if (successCount > 0 && failCount === 0) {
toast.success(`Added ${successCount} idea${successCount > 1 ? 's' : ''} to board`);
toast.success(`Added ${successCount} ${pluralizeIdea(successCount)} to board`);
} else if (successCount > 0 && failCount > 0) {
toast.warning(
`Added ${successCount} idea${successCount > 1 ? 's' : ''}, ${failCount} failed`
);
toast.warning(`Added ${successCount} ${pluralizeIdea(successCount)}, ${failCount} failed`);
} else {
toast.error('Failed to add ideas to board');
}
}, [currentProject?.path, filteredSuggestions, removeSuggestionFromJob]);
// Show discard confirmation dialog
const handleDiscardAll = useCallback(() => {
setShowDiscardConfirm(true);
}, []);
// Actually discard all filtered suggestions
const confirmDiscardAll = useCallback(() => {
const count = filteredSuggestions.length;
for (const { suggestion, job } of filteredSuggestions) {
removeSuggestionFromJob(job.id, suggestion.id);
}
toast.info(`Discarded ${count} ${pluralizeIdea(count)}`);
}, [filteredSuggestions, removeSuggestionFromJob]);
// Common readiness state for bulk operations
const bulkActionsReady = filteredSuggestions.length > 0 && !isAcceptingAll && !addingId;
// 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]);
onAcceptAllReady?.(bulkActionsReady, filteredSuggestions.length, handleAcceptAll);
}, [bulkActionsReady, filteredSuggestions.length, handleAcceptAll, onAcceptAllReady]);
// Notify parent about discard all readiness
useEffect(() => {
onDiscardAllReady?.(bulkActionsReady, filteredSuggestions.length, handleDiscardAll);
}, [bulkActionsReady, filteredSuggestions.length, handleDiscardAll, onDiscardAllReady]);
const isEmpty = allSuggestions.length === 0 && activeJobs.length === 0;
@@ -331,10 +358,10 @@ export function IdeationDashboard({ onGenerateIdeas, onAcceptAllReady }: Ideatio
{(generatingCount > 0 || allSuggestions.length > 0) && (
<p className="text-sm text-muted-foreground">
{generatingCount > 0
? `Generating ${generatingCount} idea${generatingCount > 1 ? 's' : ''}...`
? `Generating ${generatingCount} ${pluralizeIdea(generatingCount)}...`
: selectedTags.size > 0
? `Showing ${filteredSuggestions.length} of ${allSuggestions.length} ideas`
: `${allSuggestions.length} idea${allSuggestions.length > 1 ? 's' : ''} ready for review`}
? `Showing ${filteredSuggestions.length} of ${allSuggestions.length} ${pluralizeIdea(allSuggestions.length)}`
: `${allSuggestions.length} ${pluralizeIdea(allSuggestions.length)} ready for review`}
</p>
)}
@@ -419,6 +446,19 @@ export function IdeationDashboard({ onGenerateIdeas, onAcceptAllReady }: Ideatio
</Card>
)}
</div>
{/* Discard All Confirmation Dialog */}
<ConfirmDialog
open={showDiscardConfirm}
onOpenChange={setShowDiscardConfirm}
onConfirm={confirmDiscardAll}
title="Discard All Ideas"
description={`Are you sure you want to discard ${filteredSuggestions.length} ${pluralizeIdea(filteredSuggestions.length)}? This cannot be undone.`}
icon={Trash2}
iconClassName="text-destructive"
confirmText="Discard"
confirmVariant="destructive"
/>
</div>
);
}

View File

@@ -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, CheckCheck, Loader2 } from 'lucide-react';
import { ArrowLeft, ChevronRight, Lightbulb, CheckCheck, Loader2, Trash2 } from 'lucide-react';
import type { IdeaCategory } from '@automaker/types';
import type { IdeationMode } from '@/store/ideation-store';
@@ -71,6 +71,9 @@ function IdeationHeader({
acceptAllCount,
onAcceptAll,
isAcceptingAll,
discardAllReady,
discardAllCount,
onDiscardAll,
}: {
currentMode: IdeationMode;
selectedCategory: IdeaCategory | null;
@@ -81,6 +84,9 @@ function IdeationHeader({
acceptAllCount: number;
onAcceptAll: () => void;
isAcceptingAll: boolean;
discardAllReady: boolean;
discardAllCount: number;
onDiscardAll: () => void;
}) {
const { getCategoryById } = useGuidedPrompts();
const showBackButton = currentMode === 'prompts';
@@ -128,6 +134,17 @@ function IdeationHeader({
</div>
<div className="flex gap-2 items-center">
{currentMode === 'dashboard' && discardAllReady && (
<Button
onClick={onDiscardAll}
variant="outline"
className="gap-2 text-destructive hover:text-destructive"
disabled={isAcceptingAll}
>
<Trash2 className="w-4 h-4" />
Discard All ({discardAllCount})
</Button>
)}
{currentMode === 'dashboard' && acceptAllReady && (
<Button
onClick={onAcceptAll}
@@ -162,6 +179,11 @@ export function IdeationView() {
const [acceptAllHandler, setAcceptAllHandler] = useState<(() => Promise<void>) | null>(null);
const [isAcceptingAll, setIsAcceptingAll] = useState(false);
// Discard all state
const [discardAllReady, setDiscardAllReady] = useState(false);
const [discardAllCount, setDiscardAllCount] = useState(0);
const [discardAllHandler, setDiscardAllHandler] = useState<(() => void) | null>(null);
const handleAcceptAllReady = useCallback(
(isReady: boolean, count: number, handler: () => Promise<void>) => {
setAcceptAllReady(isReady);
@@ -182,6 +204,21 @@ export function IdeationView() {
}
}, [acceptAllHandler]);
const handleDiscardAllReady = useCallback(
(isReady: boolean, count: number, handler: () => void) => {
setDiscardAllReady(isReady);
setDiscardAllCount(count);
setDiscardAllHandler(() => handler);
},
[]
);
const handleDiscardAll = useCallback(() => {
if (discardAllHandler) {
discardAllHandler();
}
}, [discardAllHandler]);
const handleNavigate = useCallback(
(mode: IdeationMode, category?: IdeaCategory | null) => {
setMode(mode);
@@ -245,6 +282,9 @@ export function IdeationView() {
acceptAllCount={acceptAllCount}
onAcceptAll={handleAcceptAll}
isAcceptingAll={isAcceptingAll}
discardAllReady={discardAllReady}
discardAllCount={discardAllCount}
onDiscardAll={handleDiscardAll}
/>
{/* Dashboard - main view */}
@@ -252,6 +292,7 @@ export function IdeationView() {
<IdeationDashboard
onGenerateIdeas={handleGenerateIdeas}
onAcceptAllReady={handleAcceptAllReady}
onDiscardAllReady={handleDiscardAllReady}
/>
)}