mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 14:31:12 -05:00
84 lines
3.0 KiB
TypeScript
84 lines
3.0 KiB
TypeScript
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 = typeof item.item_data === 'object' && item.item_data !== null && !Array.isArray(item.item_data)
|
|
? item.item_data as Record<string, unknown>
|
|
: {};
|
|
const name = 'name' in data && typeof data.name === 'string' ? 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>
|
|
);
|
|
}
|