feat: Implement composite submission system

This commit is contained in:
gpt-engineer-app[bot]
2025-11-02 19:51:20 +00:00
parent 0f742f36b6
commit 3c6d6a3bdf
5 changed files with 422 additions and 11 deletions

View File

@@ -0,0 +1,81 @@
import { Badge } from '@/components/ui/badge';
import { CheckCircle2, Clock, XCircle, ChevronRight } from 'lucide-react';
import { SubmissionItemWithDeps } from '@/lib/submissionItemsService';
import { cn } from '@/lib/utils';
interface DependencyTreeViewProps {
items: SubmissionItemWithDeps[];
}
export function DependencyTreeView({ items }: DependencyTreeViewProps) {
// Build tree structure - root items (no depends_on)
const rootItems = items.filter(item => !item.depends_on);
const getStatusIcon = (status: string) => {
switch (status) {
case 'approved':
return <CheckCircle2 className="w-4 h-4 text-green-600" />;
case 'rejected':
return <XCircle className="w-4 h-4 text-destructive" />;
case 'pending':
default:
return <Clock className="w-4 h-4 text-muted-foreground" />;
}
};
const getItemLabel = (item: SubmissionItemWithDeps): string => {
const data = item.item_data;
const name = data.name || 'Unnamed';
const type = item.item_type.replace('_', ' ');
return `${name} (${type})`;
};
const renderItem = (item: SubmissionItemWithDeps, level: number = 0) => {
const dependents = items.filter(i => i.depends_on === item.id);
const hasChildren = dependents.length > 0;
return (
<div key={item.id} className={cn("space-y-2", level > 0 && "ml-6 border-l-2 border-border pl-4")}>
<div className="flex items-center gap-2">
{level > 0 && <ChevronRight className="w-4 h-4 text-muted-foreground" />}
{getStatusIcon(item.status)}
<span className={cn(
"text-sm",
item.status === 'approved' && "text-green-700 dark:text-green-400",
item.status === 'rejected' && "text-destructive",
item.status === 'pending' && "text-foreground"
)}>
{getItemLabel(item)}
</span>
<Badge variant={item.status === 'approved' ? 'default' : item.status === 'rejected' ? 'destructive' : 'secondary'} className="text-xs">
{item.status}
</Badge>
{item.depends_on && (
<Badge variant="outline" className="text-xs">
depends on parent
</Badge>
)}
</div>
{hasChildren && dependents.map(dep => renderItem(dep, level + 1))}
</div>
);
};
if (items.length <= 1) {
return null; // Don't show tree for single items
}
return (
<div className="space-y-3 p-4 border rounded-lg bg-muted/30">
<div className="flex items-center gap-2 mb-3">
<span className="text-sm font-semibold">Submission Items ({items.length})</span>
<Badge variant="outline" className="text-xs">
Composite Submission
</Badge>
</div>
<div className="space-y-2">
{rootItems.map(item => renderItem(item))}
</div>
</div>
);
}

View File

@@ -5,6 +5,7 @@ import { ArrowDown, AlertCircle } from 'lucide-react';
import { type SubmissionItemWithDeps } from '@/lib/submissionItemsService';
import { Alert, AlertDescription } from '@/components/ui/alert';
import { useIsMobile } from '@/hooks/use-mobile';
import { DependencyTreeView } from './DependencyTreeView';
interface DependencyVisualizerProps {
items: SubmissionItemWithDeps[];
@@ -45,6 +46,9 @@ export function DependencyVisualizer({ items, selectedIds }: DependencyVisualize
return (
<div className="space-y-6">
{/* Compact dependency tree view */}
<DependencyTreeView items={items} />
{hasCircularDependency && (
<Alert variant="destructive">
<AlertCircle className="h-4 w-4" />