import { useState } from 'react' import { X, CheckCircle2, Circle, SkipForward, Trash2, Loader2, AlertCircle, Pencil, Link2, AlertTriangle, UserCircle } from 'lucide-react' import { useSkipFeature, useDeleteFeature, useFeatures, useResolveHumanInput } from '../hooks/useProjects' import { EditFeatureForm } from './EditFeatureForm' import { HumanInputForm } from './HumanInputForm' import type { Feature } from '../lib/types' import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, } from '@/components/ui/dialog' import { Button } from '@/components/ui/button' import { Badge } from '@/components/ui/badge' import { Alert, AlertDescription } from '@/components/ui/alert' import { Separator } from '@/components/ui/separator' // Generate consistent color for category function getCategoryColor(category: string): string { const colors = [ 'bg-pink-500', 'bg-cyan-500', 'bg-green-500', 'bg-yellow-500', 'bg-orange-500', 'bg-purple-500', 'bg-blue-500', ] let hash = 0 for (let i = 0; i < category.length; i++) { hash = category.charCodeAt(i) + ((hash << 5) - hash) } return colors[Math.abs(hash) % colors.length] } interface FeatureModalProps { feature: Feature projectName: string onClose: () => void } export function FeatureModal({ feature, projectName, onClose }: FeatureModalProps) { const [error, setError] = useState(null) const [showDeleteConfirm, setShowDeleteConfirm] = useState(false) const [showEdit, setShowEdit] = useState(false) const skipFeature = useSkipFeature(projectName) const deleteFeature = useDeleteFeature(projectName) const { data: allFeatures } = useFeatures(projectName) const resolveHumanInput = useResolveHumanInput(projectName) // Build a map of feature ID to feature for looking up dependency names const featureMap = new Map() if (allFeatures) { ;[...allFeatures.pending, ...allFeatures.in_progress, ...allFeatures.done, ...(allFeatures.needs_human_input || [])].forEach(f => { featureMap.set(f.id, f) }) } // Get dependency features const dependencies = (feature.dependencies || []) .map(id => featureMap.get(id)) .filter((f): f is Feature => f !== undefined) // Get blocking dependencies (unmet dependencies) const blockingDeps = (feature.blocking_dependencies || []) .map(id => featureMap.get(id)) .filter((f): f is Feature => f !== undefined) const handleSkip = async () => { setError(null) try { await skipFeature.mutateAsync(feature.id) onClose() } catch (err) { setError(err instanceof Error ? err.message : 'Failed to skip feature') } } const handleDelete = async () => { setError(null) try { await deleteFeature.mutateAsync(feature.id) onClose() } catch (err) { setError(err instanceof Error ? err.message : 'Failed to delete feature') } } // Show edit form when in edit mode if (showEdit) { return ( setShowEdit(false)} onSaved={onClose} /> ) } return ( !open && onClose()}> {/* Header */}
{feature.category}
{feature.name}
{/* Content */}
{/* Error Message */} {error && ( {error} )} {/* Status */}
{feature.passes ? ( <> COMPLETE ) : feature.needs_human_input ? ( <> NEEDS YOUR INPUT ) : ( <> PENDING )} Priority: #{feature.priority}
{/* Human Input Request */} {feature.needs_human_input && feature.human_input_request && ( { setError(null) try { await resolveHumanInput.mutateAsync({ featureId: feature.id, fields }) onClose() } catch (err) { setError(err instanceof Error ? err.message : 'Failed to submit response') } }} isLoading={resolveHumanInput.isPending} /> )} {/* Previous Human Input Response */} {feature.human_input_response && !feature.needs_human_input && (

Human Input Provided

Response submitted{feature.human_input_response.responded_at ? ` at ${new Date(feature.human_input_response.responded_at).toLocaleString()}` : ''}.

)} {/* Description */}

Description

{feature.description}

{/* Blocked By Warning */} {blockingDeps.length > 0 && (

Blocked By

This feature cannot start until the following dependencies are complete:

    {blockingDeps.map(dep => (
  • #{dep.id} {dep.name}
  • ))}
)} {/* Dependencies */} {dependencies.length > 0 && (

Depends On

    {dependencies.map(dep => (
  • {dep.passes ? ( ) : ( )} #{dep.id} {dep.name}
  • ))}
)} {/* Steps */} {feature.steps.length > 0 && (

Test Steps

    {feature.steps.map((step, index) => (
  1. {step}
  2. ))}
)}
{/* Actions */} {!feature.passes && ( <> {showDeleteConfirm ? (

Are you sure you want to delete this feature?

) : (
)}
)}
) }