mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-04 21:23:07 +00:00
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:
@@ -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