feat: add discard all functionality to ideation view

- Introduced a new button in the IdeationHeader for discarding all ideas when in dashboard mode.
- Implemented state management for discard readiness and count in IdeationView.
- Added confirmation dialog for discarding ideas in IdeationDashboard.
- Enhanced bulk action readiness checks to include discard operations.

This update improves user experience by allowing bulk discarding of ideas with confirmation, ensuring actions are intentional.
This commit is contained in:
Shirone
2026-01-15 22:37:26 +01:00
parent 05a3b95d75
commit 07d800b589
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}
/>
)}