mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-01 20:23:36 +00:00
Merge branch 'v0.13.0rc' into feat/react-query
Merged latest changes from v0.13.0rc into feat/react-query while preserving React Query migration. Key merge decisions: - Kept React Query hooks for data fetching (useRunningAgents, useStopFeature, etc.) - Added backlog plan handling to running-agents-view stop functionality - Imported both SkeletonPulse and Spinner for CLI status components - Used Spinner for refresh buttons across all settings sections - Preserved isBacklogPlan check in agent-output-modal TaskProgressPanel - Added handleOpenInIntegratedTerminal to worktree actions while keeping React Query mutations
This commit is contained in:
@@ -4,10 +4,12 @@
|
||||
*/
|
||||
|
||||
import { useState, useMemo, useEffect, useCallback } from 'react';
|
||||
import { Loader2, AlertCircle, Plus, X, Sparkles, Lightbulb } from 'lucide-react';
|
||||
import { AlertCircle, Plus, X, Sparkles, Lightbulb, Trash2 } from 'lucide-react';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
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 +17,37 @@ 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' : ''}`;
|
||||
|
||||
// Helper to map priority to Badge variant
|
||||
const getPriorityVariant = (
|
||||
priority: string
|
||||
):
|
||||
| 'default'
|
||||
| 'secondary'
|
||||
| 'destructive'
|
||||
| 'outline'
|
||||
| 'success'
|
||||
| 'warning'
|
||||
| 'error'
|
||||
| 'info' => {
|
||||
switch (priority.toLowerCase()) {
|
||||
case 'high':
|
||||
return 'error';
|
||||
case 'medium':
|
||||
return 'warning';
|
||||
case 'low':
|
||||
return 'info';
|
||||
default:
|
||||
return 'secondary';
|
||||
}
|
||||
};
|
||||
|
||||
interface IdeationDashboardProps {
|
||||
onGenerateIdeas: () => void;
|
||||
onAcceptAllReady?: (isReady: boolean, count: number, handler: () => Promise<void>) => void;
|
||||
onDiscardAllReady?: (isReady: boolean, count: number, handler: () => void) => void;
|
||||
}
|
||||
|
||||
function SuggestionCard({
|
||||
@@ -34,39 +64,53 @@ function SuggestionCard({
|
||||
isAdding: boolean;
|
||||
}) {
|
||||
return (
|
||||
<Card className="transition-all hover:border-primary/50">
|
||||
<CardContent className="p-4">
|
||||
<Card className="group transition-all hover:border-primary/50 hover:shadow-sm">
|
||||
<CardContent className="p-5">
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-start gap-2 mb-1">
|
||||
<h4 className="font-medium shrink-0">{suggestion.title}</h4>
|
||||
<div className="flex-1 min-w-0 space-y-3">
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<h4 className="font-semibold text-base leading-tight">{suggestion.title}</h4>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<Badge variant="outline" className="text-xs whitespace-nowrap">
|
||||
<Badge
|
||||
variant={getPriorityVariant(suggestion.priority)}
|
||||
className="text-xs font-medium capitalize"
|
||||
>
|
||||
{suggestion.priority}
|
||||
</Badge>
|
||||
<Badge variant="secondary" className="text-xs whitespace-nowrap">
|
||||
<Badge
|
||||
variant="secondary"
|
||||
className="text-xs text-muted-foreground bg-secondary/40"
|
||||
>
|
||||
{job.prompt.title}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">{suggestion.description}</p>
|
||||
|
||||
<p className="text-sm text-muted-foreground leading-relaxed">
|
||||
{suggestion.description}
|
||||
</p>
|
||||
|
||||
{suggestion.rationale && (
|
||||
<p className="text-xs text-muted-foreground mt-2 italic">{suggestion.rationale}</p>
|
||||
<div className="relative pl-3 border-l-2 border-primary/20 mt-3 py-1">
|
||||
<p className="text-xs text-muted-foreground/80 italic">{suggestion.rationale}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2 shrink-0">
|
||||
|
||||
<div className="flex flex-col gap-2 shrink-0 pt-1">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={onRemove}
|
||||
onClick={onAccept}
|
||||
disabled={isAdding}
|
||||
className="text-muted-foreground hover:text-destructive"
|
||||
className={cn(
|
||||
'w-full gap-1.5 shadow-none transition-all',
|
||||
isAdding ? 'opacity-80' : 'hover:ring-2 hover:ring-primary/20'
|
||||
)}
|
||||
>
|
||||
<X className="w-4 h-4" />
|
||||
</Button>
|
||||
<Button size="sm" onClick={onAccept} disabled={isAdding} className="gap-1">
|
||||
{isAdding ? (
|
||||
<Loader2 className="w-4 h-4 animate-spin" />
|
||||
<Spinner size="sm" />
|
||||
) : (
|
||||
<>
|
||||
<Plus className="w-4 h-4" />
|
||||
@@ -74,6 +118,15 @@ function SuggestionCard({
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={onRemove}
|
||||
disabled={isAdding}
|
||||
className="w-full text-muted-foreground hover:text-destructive hover:bg-destructive/10 h-8"
|
||||
>
|
||||
Dismiss
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
@@ -86,19 +139,29 @@ function GeneratingCard({ job }: { job: GenerationJob }) {
|
||||
const isError = job.status === 'error';
|
||||
|
||||
return (
|
||||
<Card className={cn('transition-all', isError ? 'border-red-500/50' : 'border-blue-500/50')}>
|
||||
<CardContent className="p-4">
|
||||
<Card
|
||||
className={cn(
|
||||
'transition-all',
|
||||
isError ? 'border-destructive/50' : 'border-blue-500/30 bg-blue-50/5 dark:bg-blue-900/5'
|
||||
)}
|
||||
>
|
||||
<CardContent className="p-5">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
{isError ? (
|
||||
<AlertCircle className="w-5 h-5 text-red-500" />
|
||||
) : (
|
||||
<Loader2 className="w-5 h-5 text-blue-500 animate-spin" />
|
||||
)}
|
||||
<div className="flex items-center gap-4">
|
||||
<div
|
||||
className={cn(
|
||||
'w-10 h-10 rounded-full flex items-center justify-center shrink-0',
|
||||
isError ? 'bg-destructive/10 text-destructive' : 'bg-blue-500/10 text-blue-500'
|
||||
)}
|
||||
>
|
||||
{isError ? <AlertCircle className="w-5 h-5" /> : <Spinner size="md" />}
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-medium">{job.prompt.title}</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{isError ? job.error || 'Failed to generate' : 'Generating ideas...'}
|
||||
{isError
|
||||
? job.error || 'Failed to generate'
|
||||
: 'Analyzing codebase and generating ideas...'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -130,7 +193,7 @@ function TagFilter({
|
||||
if (tags.length === 0) return null;
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<div className="flex flex-wrap gap-2 py-2">
|
||||
{tags.map((tag) => {
|
||||
const isSelected = selectedTags.has(tag);
|
||||
const count = tagCounts[tag] || 0;
|
||||
@@ -139,28 +202,31 @@ function TagFilter({
|
||||
key={tag}
|
||||
onClick={() => onToggleTag(tag)}
|
||||
className={cn(
|
||||
'px-3 py-1.5 text-sm rounded-full border transition-all flex items-center gap-1.5',
|
||||
'px-3.5 py-1.5 text-sm rounded-full border shadow-sm transition-all flex items-center gap-2',
|
||||
isSelected
|
||||
? 'bg-primary text-primary-foreground border-primary'
|
||||
: 'bg-secondary/50 text-muted-foreground border-border hover:border-primary/50 hover:text-foreground'
|
||||
? 'bg-primary text-primary-foreground border-primary ring-2 ring-primary/20'
|
||||
: 'bg-card text-muted-foreground border-border hover:border-primary/50 hover:text-foreground hover:bg-accent/50'
|
||||
)}
|
||||
>
|
||||
{tag}
|
||||
<span className="font-medium">{tag}</span>
|
||||
<span
|
||||
className={cn(
|
||||
'text-xs',
|
||||
isSelected ? 'text-primary-foreground/70' : 'text-muted-foreground/70'
|
||||
'text-xs py-0.5 px-1.5 rounded-full',
|
||||
isSelected
|
||||
? 'bg-primary-foreground/20 text-primary-foreground'
|
||||
: 'bg-muted text-muted-foreground'
|
||||
)}
|
||||
>
|
||||
({count})
|
||||
{count}
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
{selectedTags.size > 0 && <div className="h-8 w-px bg-border mx-1" />}
|
||||
{selectedTags.size > 0 && (
|
||||
<button
|
||||
onClick={() => selectedTags.forEach((tag) => onToggleTag(tag))}
|
||||
className="px-3 py-1.5 text-sm text-muted-foreground hover:text-foreground transition-colors"
|
||||
className="px-3 py-1.5 text-sm text-muted-foreground hover:text-foreground transition-colors font-medium"
|
||||
>
|
||||
Clear filters
|
||||
</button>
|
||||
@@ -169,13 +235,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 +375,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 +419,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 +507,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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -13,8 +13,8 @@ import {
|
||||
Gauge,
|
||||
Accessibility,
|
||||
BarChart3,
|
||||
Loader2,
|
||||
} from 'lucide-react';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { useGuidedPrompts } from '@/hooks/use-guided-prompts';
|
||||
import type { IdeaCategory } from '@automaker/types';
|
||||
@@ -53,7 +53,7 @@ export function PromptCategoryGrid({ onSelect, onBack }: PromptCategoryGridProps
|
||||
|
||||
{isLoading && (
|
||||
<div className="flex items-center justify-center py-12">
|
||||
<Loader2 className="w-6 h-6 animate-spin text-muted-foreground" />
|
||||
<Spinner size="lg" />
|
||||
<span className="ml-2 text-muted-foreground">Loading categories...</span>
|
||||
</div>
|
||||
)}
|
||||
@@ -69,17 +69,19 @@ export function PromptCategoryGrid({ onSelect, onBack }: PromptCategoryGridProps
|
||||
return (
|
||||
<Card
|
||||
key={category.id}
|
||||
className="cursor-pointer transition-all hover:border-primary hover:shadow-md"
|
||||
className="group cursor-pointer transition-all duration-300 hover:border-primary hover:shadow-lg hover:-translate-y-1"
|
||||
onClick={() => onSelect(category.id)}
|
||||
>
|
||||
<CardContent className="p-6">
|
||||
<div className="flex flex-col items-center text-center gap-3">
|
||||
<div className="p-4 rounded-full bg-primary/10">
|
||||
<Icon className="w-8 h-8 text-primary" />
|
||||
<div className="flex flex-col items-center text-center gap-4">
|
||||
<div className="p-4 rounded-2xl bg-primary/10 text-primary group-hover:bg-primary/20 group-hover:scale-110 transition-all duration-300">
|
||||
<Icon className="w-8 h-8" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-semibold text-lg">{category.name}</h3>
|
||||
<p className="text-muted-foreground text-sm mt-1">{category.description}</p>
|
||||
<div className="space-y-2">
|
||||
<h3 className="font-semibold text-lg leading-tight group-hover:text-primary transition-colors">
|
||||
{category.name}
|
||||
</h3>
|
||||
<p className="text-muted-foreground text-sm">{category.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
*/
|
||||
|
||||
import { useState, useMemo } from 'react';
|
||||
import { ArrowLeft, Lightbulb, Loader2, CheckCircle2 } from 'lucide-react';
|
||||
import { ArrowLeft, Lightbulb, CheckCircle2 } from 'lucide-react';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { useGuidedPrompts } from '@/hooks/use-guided-prompts';
|
||||
import { useIdeationStore } from '@/store/ideation-store';
|
||||
@@ -113,7 +114,7 @@ export function PromptList({ category, onBack }: PromptListProps) {
|
||||
<div className="space-y-3">
|
||||
{isLoadingPrompts && (
|
||||
<div className="flex items-center justify-center py-8">
|
||||
<Loader2 className="w-6 h-6 animate-spin text-muted-foreground" />
|
||||
<Spinner size="lg" />
|
||||
<span className="ml-2 text-muted-foreground">Loading prompts...</span>
|
||||
</div>
|
||||
)}
|
||||
@@ -133,43 +134,51 @@ export function PromptList({ category, onBack }: PromptListProps) {
|
||||
return (
|
||||
<Card
|
||||
key={prompt.id}
|
||||
className={`transition-all ${
|
||||
className={`group transition-all duration-300 ${
|
||||
isDisabled
|
||||
? 'opacity-60 cursor-not-allowed'
|
||||
: 'cursor-pointer hover:border-primary hover:shadow-md'
|
||||
} ${isLoading || isGenerating ? 'border-blue-500 ring-1 ring-blue-500' : ''} ${
|
||||
isStarted && !isGenerating ? 'border-green-500/50' : ''
|
||||
? 'opacity-60 cursor-not-allowed bg-muted/50'
|
||||
: 'cursor-pointer hover:border-primary hover:shadow-md hover:-translate-x-1'
|
||||
} ${isLoading || isGenerating ? 'border-blue-500/50 ring-1 ring-blue-500/20 bg-blue-50/10' : ''} ${
|
||||
isStarted && !isGenerating ? 'border-green-500/50 bg-green-50/10' : ''
|
||||
}`}
|
||||
onClick={() => !isDisabled && handleSelectPrompt(prompt)}
|
||||
>
|
||||
<CardContent className="p-5">
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="flex items-start gap-5">
|
||||
<div
|
||||
className={`p-2 rounded-lg mt-0.5 ${
|
||||
className={`p-3 rounded-xl shrink-0 transition-all duration-300 ${
|
||||
isLoading || isGenerating
|
||||
? 'bg-blue-500/10'
|
||||
? 'bg-blue-500/10 text-blue-500'
|
||||
: isStarted
|
||||
? 'bg-green-500/10'
|
||||
: 'bg-primary/10'
|
||||
? 'bg-green-500/10 text-green-500'
|
||||
: 'bg-primary/10 text-primary group-hover:bg-primary/20 group-hover:scale-110'
|
||||
}`}
|
||||
>
|
||||
{isLoading || isGenerating ? (
|
||||
<Loader2 className="w-4 h-4 text-blue-500 animate-spin" />
|
||||
<Spinner size="md" />
|
||||
) : isStarted ? (
|
||||
<CheckCircle2 className="w-4 h-4 text-green-500" />
|
||||
<CheckCircle2 className="w-5 h-5" />
|
||||
) : (
|
||||
<Lightbulb className="w-4 h-4 text-primary" />
|
||||
<Lightbulb className="w-5 h-5" />
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<h3 className="font-semibold">{prompt.title}</h3>
|
||||
<p className="text-muted-foreground text-sm mt-1">{prompt.description}</p>
|
||||
<div className="flex-1 min-w-0 space-y-1">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<h3 className="font-semibold text-lg group-hover:text-primary transition-colors">
|
||||
{prompt.title}
|
||||
</h3>
|
||||
{isStarted && !isGenerating && (
|
||||
<span className="text-xs font-medium text-green-600 bg-green-100 dark:bg-green-900/30 dark:text-green-400 px-2 py-0.5 rounded-full">
|
||||
Generated
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-muted-foreground text-sm leading-relaxed">
|
||||
{prompt.description}
|
||||
</p>
|
||||
{(isLoading || isGenerating) && (
|
||||
<p className="text-blue-500 text-sm mt-2">Generating in dashboard...</p>
|
||||
)}
|
||||
{isStarted && !isGenerating && (
|
||||
<p className="text-green-500 text-sm mt-2">
|
||||
Already generated - check dashboard
|
||||
<p className="text-blue-500 text-sm font-medium animate-pulse pt-1">
|
||||
Generating ideas...
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,8 @@ 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, Trash2 } from 'lucide-react';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import type { IdeaCategory } from '@automaker/types';
|
||||
import type { IdeationMode } from '@/store/ideation-store';
|
||||
|
||||
@@ -71,6 +72,9 @@ function IdeationHeader({
|
||||
acceptAllCount,
|
||||
onAcceptAll,
|
||||
isAcceptingAll,
|
||||
discardAllReady,
|
||||
discardAllCount,
|
||||
onDiscardAll,
|
||||
}: {
|
||||
currentMode: IdeationMode;
|
||||
selectedCategory: IdeaCategory | null;
|
||||
@@ -81,6 +85,9 @@ function IdeationHeader({
|
||||
acceptAllCount: number;
|
||||
onAcceptAll: () => void;
|
||||
isAcceptingAll: boolean;
|
||||
discardAllReady: boolean;
|
||||
discardAllCount: number;
|
||||
onDiscardAll: () => void;
|
||||
}) {
|
||||
const { getCategoryById } = useGuidedPrompts();
|
||||
const showBackButton = currentMode === 'prompts';
|
||||
@@ -128,6 +135,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}
|
||||
@@ -135,11 +153,7 @@ function IdeationHeader({
|
||||
className="gap-2"
|
||||
disabled={isAcceptingAll}
|
||||
>
|
||||
{isAcceptingAll ? (
|
||||
<Loader2 className="w-4 h-4 animate-spin" />
|
||||
) : (
|
||||
<CheckCheck className="w-4 h-4" />
|
||||
)}
|
||||
{isAcceptingAll ? <Spinner size="sm" /> : <CheckCheck className="w-4 h-4" />}
|
||||
Accept All ({acceptAllCount})
|
||||
</Button>
|
||||
)}
|
||||
@@ -162,6 +176,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 +201,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 +279,9 @@ export function IdeationView() {
|
||||
acceptAllCount={acceptAllCount}
|
||||
onAcceptAll={handleAcceptAll}
|
||||
isAcceptingAll={isAcceptingAll}
|
||||
discardAllReady={discardAllReady}
|
||||
discardAllCount={discardAllCount}
|
||||
onDiscardAll={handleDiscardAll}
|
||||
/>
|
||||
|
||||
{/* Dashboard - main view */}
|
||||
@@ -252,6 +289,7 @@ export function IdeationView() {
|
||||
<IdeationDashboard
|
||||
onGenerateIdeas={handleGenerateIdeas}
|
||||
onAcceptAllReady={handleAcceptAllReady}
|
||||
onDiscardAllReady={handleDiscardAllReady}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user