Kraft102's picture
fix: sql.js Docker/Alpine compatibility layer for PatternMemory and FailureMemory
5a81b95
/**
* Widget Schema Viewer
*
* Komponent der visualiserer en widgets metadata/schema.
* Viser hvad widgeten kræver, leverer og kan konfigureres med.
*/
import { useState } from 'react';
import {
Info,
ArrowDownToLine,
ArrowUpFromLine,
Settings,
Link2,
Tag,
Zap,
Database,
RefreshCw
} from 'lucide-react';
import { WidgetMetadata } from '@/types/WidgetSchema';
import { widgetRegistry } from '@/lib/widgetRegistry';
import { cn } from '@/lib/utils';
interface WidgetSchemaViewerProps {
widgetId: string;
compact?: boolean;
className?: string;
}
export function WidgetSchemaViewer({ widgetId, compact = false, className }: WidgetSchemaViewerProps) {
const [activeTab, setActiveTab] = useState<'requires' | 'provides' | 'config' | 'relations'>('requires');
const metadata = widgetRegistry.get(widgetId);
if (!metadata) {
return (
<div className={cn("p-4 border border-border/30 rounded-lg bg-card/50", className)}>
<p className="text-muted-foreground text-sm">Widget metadata ikke tilgængelig</p>
</div>
);
}
if (compact) {
return <CompactView metadata={metadata} className={className} />;
}
return (
<div className={cn("border border-border/30 rounded-lg bg-card/50 overflow-hidden", className)}>
{/* Header */}
<div className="p-4 border-b border-border/30 bg-gradient-to-r from-primary/10 to-transparent">
<div className="flex items-start justify-between">
<div>
<h3 className="font-mono text-lg text-foreground">{metadata.name}</h3>
<p className="text-sm text-muted-foreground mt-1">{metadata.description}</p>
</div>
<div className="flex items-center gap-2">
<span className={cn(
"px-2 py-0.5 rounded text-xs font-mono",
metadata.priority === 'critical' && "bg-destructive/20 text-destructive",
metadata.priority === 'high' && "bg-warning/20 text-warning",
metadata.priority === 'medium' && "bg-primary/20 text-primary",
metadata.priority === 'low' && "bg-muted text-muted-foreground"
)}>
{metadata.priority || 'medium'}
</span>
<span className="text-xs text-muted-foreground font-mono">v{metadata.version}</span>
</div>
</div>
{/* Tags */}
<div className="flex flex-wrap gap-1 mt-3">
{metadata.tags.map(tag => (
<span key={tag} className="px-2 py-0.5 bg-secondary/50 rounded text-xs text-secondary-foreground flex items-center gap-1">
<Tag className="h-3 w-3" />
{tag}
</span>
))}
</div>
</div>
{/* Tabs */}
<div className="flex border-b border-border/30">
<TabButton
active={activeTab === 'requires'}
onClick={() => setActiveTab('requires')}
icon={ArrowDownToLine}
label="Kræver"
count={metadata.requires.length}
/>
<TabButton
active={activeTab === 'provides'}
onClick={() => setActiveTab('provides')}
icon={ArrowUpFromLine}
label="Leverer"
count={metadata.provides.length}
/>
<TabButton
active={activeTab === 'config'}
onClick={() => setActiveTab('config')}
icon={Settings}
label="Konfiguration"
count={metadata.config.length}
/>
<TabButton
active={activeTab === 'relations'}
onClick={() => setActiveTab('relations')}
icon={Link2}
label="Relationer"
count={metadata.relations.length}
/>
</div>
{/* Content */}
<div className="p-4 max-h-80 overflow-y-auto">
{activeTab === 'requires' && (
<div className="space-y-3">
{metadata.requires.length === 0 ? (
<p className="text-muted-foreground text-sm">Ingen data-afhængigheder</p>
) : (
metadata.requires.map(req => (
<div key={req.id} className="p-3 bg-background/50 rounded-lg border border-border/20">
<div className="flex items-center justify-between">
<span className="font-mono text-sm text-foreground">{req.name}</span>
<div className="flex items-center gap-2">
<span className="text-xs px-2 py-0.5 bg-primary/20 text-primary rounded font-mono">
{req.type}
</span>
{req.required && (
<span className="text-xs px-2 py-0.5 bg-destructive/20 text-destructive rounded">
påkrævet
</span>
)}
</div>
</div>
<p className="text-xs text-muted-foreground mt-1">{req.description}</p>
{req.refreshRate && (
<div className="flex items-center gap-1 mt-2 text-xs text-muted-foreground">
<RefreshCw className="h-3 w-3" />
Opdateres hver {req.refreshRate / 1000}s
</div>
)}
</div>
))
)}
</div>
)}
{activeTab === 'provides' && (
<div className="space-y-3">
{metadata.provides.length === 0 ? (
<p className="text-muted-foreground text-sm">Ingen outputs defineret</p>
) : (
metadata.provides.map(cap => (
<div key={cap.id} className="p-3 bg-background/50 rounded-lg border border-border/20">
<div className="flex items-center justify-between">
<span className="font-mono text-sm text-foreground">{cap.name}</span>
<span className="text-xs px-2 py-0.5 bg-accent/20 text-accent-foreground rounded font-mono">
{cap.outputType}
</span>
</div>
<p className="text-xs text-muted-foreground mt-1">{cap.description}</p>
{cap.triggers && cap.triggers.length > 0 && (
<div className="flex items-center gap-1 mt-2 flex-wrap">
<Zap className="h-3 w-3 text-warning" />
{cap.triggers.map(trigger => (
<span key={trigger} className="text-xs px-1.5 py-0.5 bg-warning/10 text-warning rounded">
{trigger}
</span>
))}
</div>
)}
</div>
))
)}
</div>
)}
{activeTab === 'config' && (
<div className="space-y-3">
{metadata.config.length === 0 ? (
<p className="text-muted-foreground text-sm">Ingen konfigurerbare indstillinger</p>
) : (
metadata.config.map(cfg => (
<div key={cfg.id} className="p-3 bg-background/50 rounded-lg border border-border/20">
<div className="flex items-center justify-between">
<span className="font-mono text-sm text-foreground">{cfg.name}</span>
<span className="text-xs px-2 py-0.5 bg-secondary/50 text-secondary-foreground rounded font-mono">
{cfg.type}
</span>
</div>
<p className="text-xs text-muted-foreground mt-1">{cfg.description}</p>
<div className="mt-2 text-xs">
<span className="text-muted-foreground">Default: </span>
<span className="font-mono text-primary">{String(cfg.default)}</span>
</div>
{cfg.options && (
<div className="flex flex-wrap gap-1 mt-2">
{cfg.options.map(opt => (
<span key={String(opt.value)} className="text-xs px-1.5 py-0.5 bg-muted rounded">
{opt.label}
</span>
))}
</div>
)}
</div>
))
)}
</div>
)}
{activeTab === 'relations' && (
<div className="space-y-3">
{metadata.relations.length === 0 ? (
<p className="text-muted-foreground text-sm">Ingen definerede relationer</p>
) : (
metadata.relations.map((rel, idx) => (
<div key={idx} className="p-3 bg-background/50 rounded-lg border border-border/20">
<div className="flex items-center gap-2">
<span className={cn(
"text-xs px-2 py-0.5 rounded font-mono",
rel.type === 'depends_on' && "bg-blue-500/20 text-blue-400",
rel.type === 'provides_to' && "bg-green-500/20 text-green-400",
rel.type === 'synchronizes' && "bg-purple-500/20 text-purple-400",
rel.type === 'triggers' && "bg-orange-500/20 text-orange-400",
rel.type === 'aggregates' && "bg-cyan-500/20 text-cyan-400"
)}>
{rel.type}
</span>
<span className="font-mono text-sm text-foreground">{rel.targetWidgetId}</span>
</div>
<p className="text-xs text-muted-foreground mt-1">{rel.description}</p>
{rel.dataFlow && (
<div className="flex items-center gap-1 mt-2 text-xs text-muted-foreground">
<Database className="h-3 w-3" />
Data: {rel.dataFlow}
</div>
)}
</div>
))
)}
</div>
)}
</div>
</div>
);
}
// Tab button component
function TabButton({
active,
onClick,
icon: Icon,
label,
count
}: {
active: boolean;
onClick: () => void;
icon: React.ElementType;
label: string;
count: number;
}) {
return (
<button
onClick={onClick}
className={cn(
"flex items-center gap-2 px-4 py-2 text-sm transition-colors",
active
? "bg-primary/10 text-primary border-b-2 border-primary"
: "text-muted-foreground hover:text-foreground hover:bg-muted/50"
)}
>
<Icon className="h-4 w-4" />
{label}
<span className={cn(
"px-1.5 py-0.5 rounded-full text-xs",
active ? "bg-primary/20" : "bg-muted"
)}>
{count}
</span>
</button>
);
}
// Compact view for inline display
function CompactView({ metadata, className }: { metadata: WidgetMetadata; className?: string }) {
return (
<div className={cn("p-2 border border-border/30 rounded bg-card/50", className)}>
<div className="flex items-center gap-2 text-xs">
<Info className="h-3 w-3 text-primary" />
<span className="font-mono text-foreground">{metadata.name}</span>
<span className="text-muted-foreground"></span>
<span className="text-muted-foreground">
{metadata.requires.length} inputs, {metadata.provides.length} outputs
</span>
</div>
</div>
);
}