mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-03 08:53:36 +00:00
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:
@@ -4,10 +4,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useState, useMemo, useEffect, useCallback } 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, Trash2 } 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';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
|
import { ConfirmDialog } from '@/components/ui/confirm-dialog';
|
||||||
import { useIdeationStore, type GenerationJob } from '@/store/ideation-store';
|
import { useIdeationStore, type GenerationJob } from '@/store/ideation-store';
|
||||||
import { useAppStore } from '@/store/app-store';
|
import { useAppStore } from '@/store/app-store';
|
||||||
import { getElectronAPI } from '@/lib/electron';
|
import { getElectronAPI } from '@/lib/electron';
|
||||||
@@ -15,9 +16,13 @@ import { toast } from 'sonner';
|
|||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import type { AnalysisSuggestion } from '@automaker/types';
|
import type { AnalysisSuggestion } from '@automaker/types';
|
||||||
|
|
||||||
|
// Helper for consistent pluralization of "idea/ideas"
|
||||||
|
const pluralizeIdea = (count: number) => `idea${count !== 1 ? 's' : ''}`;
|
||||||
|
|
||||||
interface IdeationDashboardProps {
|
interface IdeationDashboardProps {
|
||||||
onGenerateIdeas: () => void;
|
onGenerateIdeas: () => void;
|
||||||
onAcceptAllReady?: (isReady: boolean, count: number, handler: () => Promise<void>) => void;
|
onAcceptAllReady?: (isReady: boolean, count: number, handler: () => Promise<void>) => void;
|
||||||
|
onDiscardAllReady?: (isReady: boolean, count: number, handler: () => void) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SuggestionCard({
|
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 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 [isAcceptingAll, setIsAcceptingAll] = useState(false);
|
||||||
const [selectedTags, setSelectedTags] = useState<Set<string>>(new Set());
|
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)
|
// Get jobs for current project only (memoized to prevent unnecessary re-renders)
|
||||||
const projectJobs = useMemo(
|
const projectJobs = useMemo(
|
||||||
@@ -304,23 +314,40 @@ export function IdeationDashboard({ onGenerateIdeas, onAcceptAllReady }: Ideatio
|
|||||||
setIsAcceptingAll(false);
|
setIsAcceptingAll(false);
|
||||||
|
|
||||||
if (successCount > 0 && failCount === 0) {
|
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) {
|
} else if (successCount > 0 && failCount > 0) {
|
||||||
toast.warning(
|
toast.warning(`Added ${successCount} ${pluralizeIdea(successCount)}, ${failCount} failed`);
|
||||||
`Added ${successCount} idea${successCount > 1 ? 's' : ''}, ${failCount} failed`
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
toast.error('Failed to add ideas to board');
|
toast.error('Failed to add ideas to board');
|
||||||
}
|
}
|
||||||
}, [currentProject?.path, filteredSuggestions, removeSuggestionFromJob]);
|
}, [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
|
// Notify parent about accept all readiness
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (onAcceptAllReady) {
|
onAcceptAllReady?.(bulkActionsReady, filteredSuggestions.length, handleAcceptAll);
|
||||||
const isReady = filteredSuggestions.length > 0 && !isAcceptingAll && !addingId;
|
}, [bulkActionsReady, filteredSuggestions.length, handleAcceptAll, onAcceptAllReady]);
|
||||||
onAcceptAllReady(isReady, filteredSuggestions.length, handleAcceptAll);
|
|
||||||
}
|
// Notify parent about discard all readiness
|
||||||
}, [filteredSuggestions.length, isAcceptingAll, addingId, handleAcceptAll, onAcceptAllReady]);
|
useEffect(() => {
|
||||||
|
onDiscardAllReady?.(bulkActionsReady, filteredSuggestions.length, handleDiscardAll);
|
||||||
|
}, [bulkActionsReady, filteredSuggestions.length, handleDiscardAll, onDiscardAllReady]);
|
||||||
|
|
||||||
const isEmpty = allSuggestions.length === 0 && activeJobs.length === 0;
|
const isEmpty = allSuggestions.length === 0 && activeJobs.length === 0;
|
||||||
|
|
||||||
@@ -331,10 +358,10 @@ export function IdeationDashboard({ onGenerateIdeas, onAcceptAllReady }: Ideatio
|
|||||||
{(generatingCount > 0 || allSuggestions.length > 0) && (
|
{(generatingCount > 0 || allSuggestions.length > 0) && (
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
{generatingCount > 0
|
{generatingCount > 0
|
||||||
? `Generating ${generatingCount} idea${generatingCount > 1 ? 's' : ''}...`
|
? `Generating ${generatingCount} ${pluralizeIdea(generatingCount)}...`
|
||||||
: selectedTags.size > 0
|
: selectedTags.size > 0
|
||||||
? `Showing ${filteredSuggestions.length} of ${allSuggestions.length} ideas`
|
? `Showing ${filteredSuggestions.length} of ${allSuggestions.length} ${pluralizeIdea(allSuggestions.length)}`
|
||||||
: `${allSuggestions.length} idea${allSuggestions.length > 1 ? 's' : ''} ready for review`}
|
: `${allSuggestions.length} ${pluralizeIdea(allSuggestions.length)} ready for review`}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -419,6 +446,19 @@ export function IdeationDashboard({ onGenerateIdeas, onAcceptAllReady }: Ideatio
|
|||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
</div>
|
</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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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, CheckCheck, Loader2 } from 'lucide-react';
|
import { ArrowLeft, ChevronRight, Lightbulb, CheckCheck, Loader2, Trash2 } 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';
|
||||||
|
|
||||||
@@ -71,6 +71,9 @@ function IdeationHeader({
|
|||||||
acceptAllCount,
|
acceptAllCount,
|
||||||
onAcceptAll,
|
onAcceptAll,
|
||||||
isAcceptingAll,
|
isAcceptingAll,
|
||||||
|
discardAllReady,
|
||||||
|
discardAllCount,
|
||||||
|
onDiscardAll,
|
||||||
}: {
|
}: {
|
||||||
currentMode: IdeationMode;
|
currentMode: IdeationMode;
|
||||||
selectedCategory: IdeaCategory | null;
|
selectedCategory: IdeaCategory | null;
|
||||||
@@ -81,6 +84,9 @@ function IdeationHeader({
|
|||||||
acceptAllCount: number;
|
acceptAllCount: number;
|
||||||
onAcceptAll: () => void;
|
onAcceptAll: () => void;
|
||||||
isAcceptingAll: boolean;
|
isAcceptingAll: boolean;
|
||||||
|
discardAllReady: boolean;
|
||||||
|
discardAllCount: number;
|
||||||
|
onDiscardAll: () => void;
|
||||||
}) {
|
}) {
|
||||||
const { getCategoryById } = useGuidedPrompts();
|
const { getCategoryById } = useGuidedPrompts();
|
||||||
const showBackButton = currentMode === 'prompts';
|
const showBackButton = currentMode === 'prompts';
|
||||||
@@ -128,6 +134,17 @@ function IdeationHeader({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex gap-2 items-center">
|
<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 && (
|
{currentMode === 'dashboard' && acceptAllReady && (
|
||||||
<Button
|
<Button
|
||||||
onClick={onAcceptAll}
|
onClick={onAcceptAll}
|
||||||
@@ -162,6 +179,11 @@ export function IdeationView() {
|
|||||||
const [acceptAllHandler, setAcceptAllHandler] = useState<(() => Promise<void>) | null>(null);
|
const [acceptAllHandler, setAcceptAllHandler] = useState<(() => Promise<void>) | null>(null);
|
||||||
const [isAcceptingAll, setIsAcceptingAll] = useState(false);
|
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(
|
const handleAcceptAllReady = useCallback(
|
||||||
(isReady: boolean, count: number, handler: () => Promise<void>) => {
|
(isReady: boolean, count: number, handler: () => Promise<void>) => {
|
||||||
setAcceptAllReady(isReady);
|
setAcceptAllReady(isReady);
|
||||||
@@ -182,6 +204,21 @@ export function IdeationView() {
|
|||||||
}
|
}
|
||||||
}, [acceptAllHandler]);
|
}, [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(
|
const handleNavigate = useCallback(
|
||||||
(mode: IdeationMode, category?: IdeaCategory | null) => {
|
(mode: IdeationMode, category?: IdeaCategory | null) => {
|
||||||
setMode(mode);
|
setMode(mode);
|
||||||
@@ -245,6 +282,9 @@ export function IdeationView() {
|
|||||||
acceptAllCount={acceptAllCount}
|
acceptAllCount={acceptAllCount}
|
||||||
onAcceptAll={handleAcceptAll}
|
onAcceptAll={handleAcceptAll}
|
||||||
isAcceptingAll={isAcceptingAll}
|
isAcceptingAll={isAcceptingAll}
|
||||||
|
discardAllReady={discardAllReady}
|
||||||
|
discardAllCount={discardAllCount}
|
||||||
|
onDiscardAll={handleDiscardAll}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Dashboard - main view */}
|
{/* Dashboard - main view */}
|
||||||
@@ -252,6 +292,7 @@ export function IdeationView() {
|
|||||||
<IdeationDashboard
|
<IdeationDashboard
|
||||||
onGenerateIdeas={handleGenerateIdeas}
|
onGenerateIdeas={handleGenerateIdeas}
|
||||||
onAcceptAllReady={handleAcceptAllReady}
|
onAcceptAllReady={handleAcceptAllReady}
|
||||||
|
onDiscardAllReady={handleDiscardAllReady}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user