Merge pull request #1817 from AndyMik90/auto-claude/226-add-archive-button-to-done-tasks
auto-claude: 226-add-archive-button-to-done-tasks
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Archive } from 'lucide-react';
|
||||
import { RoadmapGenerationProgress } from './RoadmapGenerationProgress';
|
||||
import { CompetitorAnalysisDialog } from './CompetitorAnalysisDialog';
|
||||
import { ExistingCompetitorAnalysisDialog } from './ExistingCompetitorAnalysisDialog';
|
||||
@@ -8,17 +10,30 @@ import { RoadmapHeader } from './roadmap/RoadmapHeader';
|
||||
import { RoadmapEmptyState } from './roadmap/RoadmapEmptyState';
|
||||
import { RoadmapTabs } from './roadmap/RoadmapTabs';
|
||||
import { FeatureDetailPanel } from './roadmap/FeatureDetailPanel';
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogContent,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogCancel,
|
||||
AlertDialogAction,
|
||||
} from './ui/alert-dialog';
|
||||
import { useRoadmapData, useFeatureActions, useRoadmapGeneration, useRoadmapSave, useFeatureDelete } from './roadmap/hooks';
|
||||
import { getCompetitorInsightsForFeature } from './roadmap/utils';
|
||||
import type { RoadmapFeature } from '../../shared/types';
|
||||
import type { RoadmapProps } from './roadmap/types';
|
||||
|
||||
export function Roadmap({ projectId, onGoToTask }: RoadmapProps) {
|
||||
const { t } = useTranslation('common');
|
||||
|
||||
// State management
|
||||
const [selectedFeature, setSelectedFeature] = useState<RoadmapFeature | null>(null);
|
||||
const [activeTab, setActiveTab] = useState('kanban');
|
||||
const [showAddFeatureDialog, setShowAddFeatureDialog] = useState(false);
|
||||
const [showCompetitorViewer, setShowCompetitorViewer] = useState(false);
|
||||
const [pendingArchiveFeatureId, setPendingArchiveFeatureId] = useState<string | null>(null);
|
||||
|
||||
// Custom hooks
|
||||
const { roadmap, competitorAnalysis, generationStatus } = useRoadmapData(projectId);
|
||||
@@ -54,6 +69,22 @@ export function Roadmap({ projectId, onGoToTask }: RoadmapProps) {
|
||||
}
|
||||
};
|
||||
|
||||
const handleArchiveFeature = (featureId: string) => {
|
||||
setPendingArchiveFeatureId(featureId);
|
||||
};
|
||||
|
||||
const confirmArchiveFeature = async () => {
|
||||
if (!pendingArchiveFeatureId) return;
|
||||
try {
|
||||
await deleteFeature(pendingArchiveFeatureId);
|
||||
if (selectedFeature?.id === pendingArchiveFeatureId) {
|
||||
setSelectedFeature(null);
|
||||
}
|
||||
} finally {
|
||||
setPendingArchiveFeatureId(null);
|
||||
}
|
||||
};
|
||||
|
||||
// Show generation progress
|
||||
if (generationStatus.phase !== 'idle' && generationStatus.phase !== 'complete') {
|
||||
return (
|
||||
@@ -116,6 +147,7 @@ export function Roadmap({ projectId, onGoToTask }: RoadmapProps) {
|
||||
onConvertToSpec={handleConvertToSpec}
|
||||
onGoToTask={handleGoToTask}
|
||||
onSave={saveRoadmap}
|
||||
onArchive={handleArchiveFeature}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -127,6 +159,7 @@ export function Roadmap({ projectId, onGoToTask }: RoadmapProps) {
|
||||
onConvertToSpec={handleConvertToSpec}
|
||||
onGoToTask={handleGoToTask}
|
||||
onDelete={deleteFeature}
|
||||
onArchive={handleArchiveFeature}
|
||||
competitorInsights={getCompetitorInsightsForFeature(selectedFeature, competitorAnalysis)}
|
||||
/>
|
||||
)}
|
||||
@@ -165,6 +198,34 @@ export function Roadmap({ projectId, onGoToTask }: RoadmapProps) {
|
||||
open={showAddFeatureDialog}
|
||||
onOpenChange={setShowAddFeatureDialog}
|
||||
/>
|
||||
|
||||
{/* Archive Confirmation Dialog */}
|
||||
<AlertDialog
|
||||
open={!!pendingArchiveFeatureId}
|
||||
onOpenChange={(open) => { if (!open) setPendingArchiveFeatureId(null); }}
|
||||
>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<div className="flex items-center gap-2">
|
||||
<Archive className="h-5 w-5 text-muted-foreground" />
|
||||
<AlertDialogTitle>{t('roadmap.archiveFeatureConfirmTitle')}</AlertDialogTitle>
|
||||
</div>
|
||||
<AlertDialogDescription>
|
||||
{t('roadmap.archiveFeatureConfirmDescription', {
|
||||
title: pendingArchiveFeatureId
|
||||
? roadmap.features.find((f) => f.id === pendingArchiveFeatureId)?.title ?? ''
|
||||
: '',
|
||||
})}
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>{t('buttons.cancel')}</AlertDialogCancel>
|
||||
<AlertDialogAction onClick={confirmArchiveFeature}>
|
||||
{t('roadmap.archiveFeature')}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ interface RoadmapKanbanViewProps {
|
||||
onConvertToSpec?: (feature: RoadmapFeature) => void;
|
||||
onGoToTask?: (specId: string) => void;
|
||||
onSave?: () => void;
|
||||
onArchive?: (featureId: string) => void;
|
||||
}
|
||||
|
||||
interface DroppableStatusColumnProps {
|
||||
@@ -45,6 +46,7 @@ interface DroppableStatusColumnProps {
|
||||
onFeatureClick: (feature: RoadmapFeature) => void;
|
||||
onConvertToSpec?: (feature: RoadmapFeature) => void;
|
||||
onGoToTask?: (specId: string) => void;
|
||||
onArchive?: (featureId: string) => void;
|
||||
isOver: boolean;
|
||||
}
|
||||
|
||||
@@ -71,6 +73,7 @@ function DroppableStatusColumn({
|
||||
onFeatureClick,
|
||||
onConvertToSpec,
|
||||
onGoToTask,
|
||||
onArchive,
|
||||
isOver
|
||||
}: DroppableStatusColumnProps) {
|
||||
const { setNodeRef } = useDroppable({
|
||||
@@ -158,6 +161,7 @@ function DroppableStatusColumn({
|
||||
onClick={() => onFeatureClick(feature)}
|
||||
onConvertToSpec={onConvertToSpec}
|
||||
onGoToTask={onGoToTask}
|
||||
onArchive={onArchive}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
@@ -174,7 +178,8 @@ export function RoadmapKanbanView({
|
||||
onFeatureClick,
|
||||
onConvertToSpec,
|
||||
onGoToTask,
|
||||
onSave
|
||||
onSave,
|
||||
onArchive
|
||||
}: RoadmapKanbanViewProps) {
|
||||
const [activeFeature, setActiveFeature] = useState<RoadmapFeature | null>(null);
|
||||
const [overColumnId, setOverColumnId] = useState<string | null>(null);
|
||||
@@ -300,6 +305,7 @@ export function RoadmapKanbanView({
|
||||
onFeatureClick={onFeatureClick}
|
||||
onConvertToSpec={onConvertToSpec}
|
||||
onGoToTask={onGoToTask}
|
||||
onArchive={onArchive}
|
||||
isOver={overColumnId === column.id}
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -9,7 +9,8 @@ import {
|
||||
TooltipContent,
|
||||
TooltipTrigger
|
||||
} from './ui/tooltip';
|
||||
import { Play, ExternalLink, TrendingUp, Layers, ThumbsUp } from 'lucide-react';
|
||||
import { Play, ExternalLink, TrendingUp, Layers, ThumbsUp, Archive } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { TaskOutcomeBadge, getTaskOutcomeColorClass } from './roadmap/TaskOutcomeBadge';
|
||||
import {
|
||||
ROADMAP_PRIORITY_COLORS,
|
||||
@@ -25,6 +26,7 @@ interface SortableFeatureCardProps {
|
||||
onClick: () => void;
|
||||
onConvertToSpec?: (feature: RoadmapFeature) => void;
|
||||
onGoToTask?: (specId: string) => void;
|
||||
onArchive?: (featureId: string) => void;
|
||||
}
|
||||
|
||||
export function SortableFeatureCard({
|
||||
@@ -32,8 +34,10 @@ export function SortableFeatureCard({
|
||||
roadmap,
|
||||
onClick,
|
||||
onConvertToSpec,
|
||||
onGoToTask
|
||||
onGoToTask,
|
||||
onArchive
|
||||
}: SortableFeatureCardProps) {
|
||||
const { t } = useTranslation('common');
|
||||
const {
|
||||
attributes,
|
||||
listeners,
|
||||
@@ -120,7 +124,7 @@ export function SortableFeatureCard({
|
||||
</div>
|
||||
<h3 className="font-medium text-sm leading-snug line-clamp-2">{feature.title}</h3>
|
||||
</div>
|
||||
<div className="shrink-0">
|
||||
<div className="shrink-0 flex items-center gap-1">
|
||||
{feature.taskOutcome ? (
|
||||
<Badge
|
||||
variant="outline"
|
||||
@@ -139,7 +143,7 @@ export function SortableFeatureCard({
|
||||
}}
|
||||
>
|
||||
<ExternalLink className="h-3 w-3 mr-1" />
|
||||
Task
|
||||
{t('roadmap.task')}
|
||||
</Button>
|
||||
) : (
|
||||
feature.status !== 'done' &&
|
||||
@@ -154,10 +158,25 @@ export function SortableFeatureCard({
|
||||
}}
|
||||
>
|
||||
<Play className="h-3 w-3 mr-1" />
|
||||
Build
|
||||
{t('roadmap.build')}
|
||||
</Button>
|
||||
)
|
||||
)}
|
||||
{feature.status === 'done' && onArchive && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-7 px-2"
|
||||
title={t('roadmap.archiveFeature')}
|
||||
aria-label={t('accessibility.archiveFeatureAriaLabel')}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onArchive(feature.id);
|
||||
}}
|
||||
>
|
||||
<Archive className="h-3 w-3" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ExternalLink, Play, TrendingUp } from 'lucide-react';
|
||||
import { Archive, ExternalLink, Play, TrendingUp } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { TaskOutcomeBadge, getTaskOutcomeColorClass } from './TaskOutcomeBadge';
|
||||
import { Badge } from '../ui/badge';
|
||||
import { Button } from '../ui/button';
|
||||
@@ -17,8 +18,10 @@ export function FeatureCard({
|
||||
onClick,
|
||||
onConvertToSpec,
|
||||
onGoToTask,
|
||||
onArchive,
|
||||
hasCompetitorInsight = false,
|
||||
}: FeatureCardProps) {
|
||||
const { t } = useTranslation('common');
|
||||
|
||||
return (
|
||||
<Card className="p-4 hover:bg-muted/50 cursor-pointer transition-colors" onClick={onClick}>
|
||||
@@ -55,37 +58,53 @@ export function FeatureCard({
|
||||
<h3 className="font-medium">{feature.title}</h3>
|
||||
<p className="text-sm text-muted-foreground line-clamp-2">{feature.description}</p>
|
||||
</div>
|
||||
{feature.taskOutcome ? (
|
||||
<Badge variant="outline" className={`text-xs ${getTaskOutcomeColorClass(feature.taskOutcome)}`}>
|
||||
<TaskOutcomeBadge outcome={feature.taskOutcome} size="md" />
|
||||
</Badge>
|
||||
) : feature.linkedSpecId ? (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onGoToTask(feature.linkedSpecId!);
|
||||
}}
|
||||
>
|
||||
<ExternalLink className="h-3 w-3 mr-1" />
|
||||
Go to Task
|
||||
</Button>
|
||||
) : (
|
||||
feature.status !== 'done' && (
|
||||
<div className="flex items-center gap-1">
|
||||
{feature.taskOutcome ? (
|
||||
<Badge variant="outline" className={`text-xs ${getTaskOutcomeColorClass(feature.taskOutcome)}`}>
|
||||
<TaskOutcomeBadge outcome={feature.taskOutcome} size="md" />
|
||||
</Badge>
|
||||
) : feature.linkedSpecId ? (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onConvertToSpec(feature);
|
||||
onGoToTask(feature.linkedSpecId!);
|
||||
}}
|
||||
>
|
||||
<Play className="h-3 w-3 mr-1" />
|
||||
Build
|
||||
<ExternalLink className="h-3 w-3 mr-1" />
|
||||
{t('roadmap.goToTask')}
|
||||
</Button>
|
||||
)
|
||||
)}
|
||||
) : (
|
||||
feature.status !== 'done' && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onConvertToSpec(feature);
|
||||
}}
|
||||
>
|
||||
<Play className="h-3 w-3 mr-1" />
|
||||
{t('roadmap.build')}
|
||||
</Button>
|
||||
)
|
||||
)}
|
||||
{feature.status === 'done' && onArchive && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
title={t('roadmap.archiveFeature')}
|
||||
aria-label={t('accessibility.archiveFeatureAriaLabel')}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onArchive(feature.id);
|
||||
}}
|
||||
>
|
||||
<Archive className="h-3 w-3" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
ExternalLink,
|
||||
TrendingUp,
|
||||
Trash2,
|
||||
Archive,
|
||||
} from 'lucide-react';
|
||||
import { TaskOutcomeBadge } from './TaskOutcomeBadge';
|
||||
import { Badge } from '../ui/badge';
|
||||
@@ -31,11 +32,16 @@ export function FeatureDetailPanel({
|
||||
onConvertToSpec,
|
||||
onGoToTask,
|
||||
onDelete,
|
||||
onArchive,
|
||||
competitorInsights = [],
|
||||
}: FeatureDetailPanelProps) {
|
||||
const { t } = useTranslation('common');
|
||||
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
||||
|
||||
const handleArchive = () => {
|
||||
onArchive?.(feature.id);
|
||||
};
|
||||
|
||||
const handleDelete = () => {
|
||||
if (onDelete) {
|
||||
onDelete(feature.id);
|
||||
@@ -215,29 +221,53 @@ export function FeatureDetailPanel({
|
||||
</ScrollArea>
|
||||
|
||||
{/* Actions */}
|
||||
{feature.taskOutcome ? (
|
||||
<div className="shrink-0 p-4 border-t border-border">
|
||||
<div className="flex items-center justify-center gap-2 py-2">
|
||||
<TaskOutcomeBadge outcome={feature.taskOutcome} size="lg" />
|
||||
</div>
|
||||
</div>
|
||||
) : feature.linkedSpecId ? (
|
||||
<div className="shrink-0 p-4 border-t border-border">
|
||||
<Button className="w-full" onClick={() => onGoToTask(feature.linkedSpecId!)}>
|
||||
<ExternalLink className="h-4 w-4 mr-2" />
|
||||
Go to Task
|
||||
{(() => {
|
||||
const archiveButton = feature.status === 'done' && (
|
||||
<Button
|
||||
variant="outline"
|
||||
className="w-full"
|
||||
onClick={handleArchive}
|
||||
aria-label={t('accessibility.archiveFeatureAriaLabel')}
|
||||
>
|
||||
<Archive className="h-4 w-4 mr-2" />
|
||||
{t('roadmap.archiveFeature')}
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
feature.status !== 'done' && (
|
||||
);
|
||||
|
||||
if (feature.taskOutcome) return (
|
||||
<div className="shrink-0 p-4 border-t border-border space-y-3">
|
||||
<div className="flex items-center justify-center gap-2 py-2">
|
||||
<TaskOutcomeBadge outcome={feature.taskOutcome} size="lg" />
|
||||
</div>
|
||||
{archiveButton}
|
||||
</div>
|
||||
);
|
||||
|
||||
if (feature.linkedSpecId) return (
|
||||
<div className="shrink-0 p-4 border-t border-border space-y-2">
|
||||
<Button className="w-full" onClick={() => onGoToTask(feature.linkedSpecId!)}>
|
||||
<ExternalLink className="h-4 w-4 mr-2" />
|
||||
{t('roadmap.goToTask')}
|
||||
</Button>
|
||||
{archiveButton}
|
||||
</div>
|
||||
);
|
||||
|
||||
if (feature.status === 'done') return (
|
||||
<div className="shrink-0 p-4 border-t border-border">
|
||||
{archiveButton}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="shrink-0 p-4 border-t border-border">
|
||||
<Button className="w-full" onClick={() => onConvertToSpec(feature)}>
|
||||
<Zap className="h-4 w-4 mr-2" />
|
||||
Convert to Auto-Build Task
|
||||
{t('roadmap.convertToTask')}
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
);
|
||||
})()}
|
||||
|
||||
{/* Delete Confirmation */}
|
||||
{showDeleteConfirm && (
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState } from 'react';
|
||||
import { CheckCircle2, ChevronDown, ChevronUp, Circle, ExternalLink, Play, TrendingUp } from 'lucide-react';
|
||||
import { Archive, CheckCircle2, ChevronDown, ChevronUp, Circle, ExternalLink, Play, TrendingUp } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { TaskOutcomeBadge } from './TaskOutcomeBadge';
|
||||
import { Badge } from '../ui/badge';
|
||||
@@ -18,6 +18,7 @@ export function PhaseCard({
|
||||
onFeatureSelect,
|
||||
onConvertToSpec,
|
||||
onGoToTask,
|
||||
onArchive,
|
||||
}: PhaseCardProps) {
|
||||
const { t } = useTranslation('common');
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
@@ -96,7 +97,24 @@ export function PhaseCard({
|
||||
<div>
|
||||
<h4 className="text-sm font-medium mb-2">Features ({features.length})</h4>
|
||||
<div className="grid gap-2">
|
||||
{visibleFeatures.map((feature) => (
|
||||
{visibleFeatures.map((feature) => {
|
||||
const isDone = feature.status === 'done';
|
||||
const archiveButton = isDone && onArchive && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-6 px-2"
|
||||
title={t('roadmap.archiveFeature')}
|
||||
aria-label={t('accessibility.archiveFeatureAriaLabel')}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onArchive(feature.id);
|
||||
}}
|
||||
>
|
||||
<Archive className="h-3 w-3" />
|
||||
</Button>
|
||||
);
|
||||
return (
|
||||
<div
|
||||
key={feature.id}
|
||||
className="flex items-center justify-between p-2 rounded-md bg-muted/50 hover:bg-muted transition-colors"
|
||||
@@ -118,11 +136,15 @@ export function PhaseCard({
|
||||
)}
|
||||
</button>
|
||||
{feature.taskOutcome ? (
|
||||
<span className="flex-shrink-0">
|
||||
<span className="flex items-center gap-1 flex-shrink-0">
|
||||
<TaskOutcomeBadge outcome={feature.taskOutcome} size="lg" showLabel={false} />
|
||||
{archiveButton}
|
||||
</span>
|
||||
) : isDone ? (
|
||||
<span className="flex items-center gap-1 flex-shrink-0">
|
||||
<CheckCircle2 className="h-4 w-4 text-success" />
|
||||
{archiveButton}
|
||||
</span>
|
||||
) : feature.status === 'done' ? (
|
||||
<CheckCircle2 className="h-4 w-4 text-success flex-shrink-0" />
|
||||
) : feature.linkedSpecId ? (
|
||||
<Button
|
||||
variant="ghost"
|
||||
@@ -134,7 +156,7 @@ export function PhaseCard({
|
||||
}}
|
||||
>
|
||||
<ExternalLink className="h-3 w-3 mr-1" />
|
||||
View Task
|
||||
{t('roadmap.viewTask')}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
@@ -147,11 +169,12 @@ export function PhaseCard({
|
||||
}}
|
||||
>
|
||||
<Play className="h-3 w-3 mr-1" />
|
||||
Build
|
||||
{t('roadmap.build')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
{hasMoreFeatures && (
|
||||
<Button
|
||||
type="button"
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { TrendingUp } from 'lucide-react';
|
||||
import { Archive, TrendingUp, CheckCircle2 } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Badge } from '../ui/badge';
|
||||
import { Button } from '../ui/button';
|
||||
import { Card } from '../ui/card';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '../ui/tabs';
|
||||
import { PhaseCard } from './PhaseCard';
|
||||
@@ -23,8 +25,10 @@ export function RoadmapTabs({
|
||||
onFeatureSelect,
|
||||
onConvertToSpec,
|
||||
onGoToTask,
|
||||
onArchive,
|
||||
onSave,
|
||||
}: RoadmapTabsProps) {
|
||||
const { t } = useTranslation('common');
|
||||
return (
|
||||
<Tabs value={activeTab} onValueChange={onTabChange} className="h-full flex flex-col">
|
||||
<TabsList className="shrink-0 mx-4 mt-4">
|
||||
@@ -42,6 +46,7 @@ export function RoadmapTabs({
|
||||
onFeatureClick={onFeatureSelect}
|
||||
onConvertToSpec={onConvertToSpec}
|
||||
onGoToTask={onGoToTask}
|
||||
onArchive={onArchive}
|
||||
onSave={onSave}
|
||||
/>
|
||||
</TabsContent>
|
||||
@@ -58,6 +63,7 @@ export function RoadmapTabs({
|
||||
onFeatureSelect={onFeatureSelect}
|
||||
onConvertToSpec={onConvertToSpec}
|
||||
onGoToTask={onGoToTask}
|
||||
onArchive={onArchive}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
@@ -73,6 +79,7 @@ export function RoadmapTabs({
|
||||
onClick={() => onFeatureSelect(feature)}
|
||||
onConvertToSpec={onConvertToSpec}
|
||||
onGoToTask={onGoToTask}
|
||||
onArchive={onArchive}
|
||||
hasCompetitorInsight={hasCompetitorInsight(feature)}
|
||||
/>
|
||||
))}
|
||||
@@ -93,35 +100,64 @@ export function RoadmapTabs({
|
||||
<span className="text-sm text-muted-foreground">{features.length} features</span>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
{features.map((feature: RoadmapFeature) => (
|
||||
<div
|
||||
key={feature.id}
|
||||
className="p-2 rounded-md bg-muted/50 hover:bg-muted cursor-pointer transition-colors"
|
||||
onClick={() => onFeatureSelect(feature)}
|
||||
>
|
||||
<div className="font-medium text-sm">{feature.title}</div>
|
||||
<div className="flex items-center gap-2 mt-1 flex-wrap">
|
||||
<Badge
|
||||
variant="outline"
|
||||
className={`text-xs ${ROADMAP_COMPLEXITY_COLORS[feature.complexity]}`}
|
||||
{features.map((feature: RoadmapFeature) => {
|
||||
const isDone = feature.status === 'done';
|
||||
return (
|
||||
<div
|
||||
key={feature.id}
|
||||
className="p-2 rounded-md bg-muted/50 hover:bg-muted transition-colors"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
className="w-full text-left cursor-pointer rounded-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
|
||||
onClick={() => onFeatureSelect(feature)}
|
||||
>
|
||||
{feature.complexity}
|
||||
</Badge>
|
||||
<Badge
|
||||
variant="outline"
|
||||
className={`text-xs ${ROADMAP_IMPACT_COLORS[feature.impact]}`}
|
||||
>
|
||||
{feature.impact} impact
|
||||
</Badge>
|
||||
{hasCompetitorInsight(feature) && (
|
||||
<Badge variant="outline" className="text-xs text-primary border-primary/50">
|
||||
<TrendingUp className="h-3 w-3 mr-1" />
|
||||
Insight
|
||||
</Badge>
|
||||
<div className="font-medium text-sm">{feature.title}</div>
|
||||
<div className="flex items-center gap-2 mt-1 flex-wrap">
|
||||
<Badge
|
||||
variant="outline"
|
||||
className={`text-xs ${ROADMAP_COMPLEXITY_COLORS[feature.complexity]}`}
|
||||
>
|
||||
{feature.complexity}
|
||||
</Badge>
|
||||
<Badge
|
||||
variant="outline"
|
||||
className={`text-xs ${ROADMAP_IMPACT_COLORS[feature.impact]}`}
|
||||
>
|
||||
{feature.impact} impact
|
||||
</Badge>
|
||||
{hasCompetitorInsight(feature) && (
|
||||
<Badge variant="outline" className="text-xs text-primary border-primary/50">
|
||||
<TrendingUp className="h-3 w-3 mr-1" />
|
||||
Insight
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
{isDone && onArchive && (
|
||||
<div className="flex items-center justify-between mt-2 pt-2 border-t border-border/50">
|
||||
<span className="flex items-center gap-1 text-xs text-muted-foreground">
|
||||
<CheckCircle2 className="h-3 w-3 text-success" />
|
||||
Completed
|
||||
</span>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-6 px-2"
|
||||
title={t('roadmap.archiveFeature')}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onArchive(feature.id);
|
||||
}}
|
||||
>
|
||||
<Archive className="h-3 w-3 mr-1" />
|
||||
Archive
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
|
||||
@@ -114,6 +114,7 @@ export function useFeatureDelete(projectId: string) {
|
||||
return { deleteFeature: handleDeleteFeature };
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Hook to manage roadmap generation actions
|
||||
*
|
||||
|
||||
@@ -12,6 +12,7 @@ export interface PhaseCardProps {
|
||||
onFeatureSelect: (feature: RoadmapFeature) => void;
|
||||
onConvertToSpec: (feature: RoadmapFeature) => void;
|
||||
onGoToTask: (specId: string) => void;
|
||||
onArchive?: (featureId: string) => void;
|
||||
}
|
||||
|
||||
export interface FeatureCardProps {
|
||||
@@ -20,6 +21,7 @@ export interface FeatureCardProps {
|
||||
onConvertToSpec: (feature: RoadmapFeature) => void;
|
||||
onGoToTask: (specId: string) => void;
|
||||
hasCompetitorInsight?: boolean;
|
||||
onArchive?: (featureId: string) => void;
|
||||
}
|
||||
|
||||
export interface FeatureDetailPanelProps {
|
||||
@@ -28,6 +30,7 @@ export interface FeatureDetailPanelProps {
|
||||
onConvertToSpec: (feature: RoadmapFeature) => void;
|
||||
onGoToTask: (specId: string) => void;
|
||||
onDelete?: (featureId: string) => void;
|
||||
onArchive?: (featureId: string) => void;
|
||||
competitorInsights?: CompetitorPainPoint[];
|
||||
}
|
||||
|
||||
@@ -51,4 +54,5 @@ export interface RoadmapTabsProps {
|
||||
onConvertToSpec: (feature: RoadmapFeature) => void;
|
||||
onGoToTask: (specId: string) => void;
|
||||
onSave?: () => void;
|
||||
onArchive?: (featureId: string) => void;
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
},
|
||||
"accessibility": {
|
||||
"deleteFeatureAriaLabel": "Delete feature",
|
||||
"archiveFeatureAriaLabel": "Archive feature",
|
||||
"closeFeatureDetailsAriaLabel": "Close feature details",
|
||||
"regenerateRoadmapAriaLabel": "Regenerate Roadmap",
|
||||
"repositoryOwnerAriaLabel": "Repository owner",
|
||||
@@ -625,7 +626,15 @@
|
||||
"taskArchived": "Archived",
|
||||
"showMoreFeatures": "Show {{count}} more feature",
|
||||
"showMoreFeatures_plural": "Show {{count}} more features",
|
||||
"showLessFeatures": "Show less"
|
||||
"showLessFeatures": "Show less",
|
||||
"archiveFeature": "Archive",
|
||||
"archiveFeatureConfirmTitle": "Archive Feature?",
|
||||
"archiveFeatureConfirmDescription": "This will remove \"{{title}}\" from your roadmap.",
|
||||
"goToTask": "Go to Task",
|
||||
"convertToTask": "Convert to Auto-Build Task",
|
||||
"build": "Build",
|
||||
"task": "Task",
|
||||
"viewTask": "View Task"
|
||||
},
|
||||
"roadmapGeneration": {
|
||||
"progress": "Progress",
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
},
|
||||
"accessibility": {
|
||||
"deleteFeatureAriaLabel": "Supprimer la fonctionnalité",
|
||||
"archiveFeatureAriaLabel": "Archiver la fonctionnalité",
|
||||
"closeFeatureDetailsAriaLabel": "Fermer les détails de la fonctionnalité",
|
||||
"regenerateRoadmapAriaLabel": "Régénérer la feuille de route",
|
||||
"repositoryOwnerAriaLabel": "Propriétaire du dépôt",
|
||||
@@ -625,7 +626,15 @@
|
||||
"taskArchived": "Archivé",
|
||||
"showMoreFeatures": "Afficher {{count}} fonctionnalité supplémentaire",
|
||||
"showMoreFeatures_plural": "Afficher {{count}} fonctionnalités supplémentaires",
|
||||
"showLessFeatures": "Afficher moins"
|
||||
"showLessFeatures": "Afficher moins",
|
||||
"archiveFeature": "Archiver",
|
||||
"archiveFeatureConfirmTitle": "Archiver la fonctionnalité ?",
|
||||
"archiveFeatureConfirmDescription": "Cela supprimera \"{{title}}\" de votre feuille de route.",
|
||||
"goToTask": "Aller à la tâche",
|
||||
"convertToTask": "Convertir en tâche Auto-Build",
|
||||
"build": "Construire",
|
||||
"task": "Tâche",
|
||||
"viewTask": "Voir la tâche"
|
||||
},
|
||||
"roadmapGeneration": {
|
||||
"progress": "Progression",
|
||||
|
||||
Reference in New Issue
Block a user