- Custom color system (surface-0..4, accent, mint) replacing raw gray-xxx - Inter + JetBrains Mono fonts via Google Fonts - Refined sidebar: compact logo, geometric icons, subtle active states - Fixed bg-gray-850 bug (invalid Tailwind class) in 9 components - Polished login page with centered card + gradient logo - Unified card/table/button/input styling across all components - Subtle grain texture overlay for depth - Smoother animations (fade-in, slide-up)
131 lines
5.5 KiB
TypeScript
131 lines
5.5 KiB
TypeScript
import { useState, useEffect } from 'react'
|
|
import { getProjectionDefs } from '../api'
|
|
import { Spinner, EmptyState, HashBadge } from './Common'
|
|
|
|
export default function ProjectionDefs() {
|
|
const [data, setData] = useState<any[]>([])
|
|
const [expanded, setExpanded] = useState<Set<string>>(new Set())
|
|
const [error, setError] = useState('')
|
|
const [loading, setLoading] = useState(true)
|
|
|
|
useEffect(() => {
|
|
getProjectionDefs()
|
|
.then((res) => setData(res.projection_defs))
|
|
.catch((e) => setError(e.message))
|
|
.finally(() => setLoading(false))
|
|
}, [])
|
|
|
|
const toggleExpand = (hash: string) => {
|
|
setExpanded((prev) => {
|
|
const next = new Set(prev)
|
|
if (next.has(hash)) {
|
|
next.delete(hash)
|
|
} else {
|
|
next.add(hash)
|
|
}
|
|
return next
|
|
})
|
|
}
|
|
|
|
if (loading) return <Spinner />
|
|
if (error) return <div className="text-red-500 text-center p-8">Error: {error}</div>
|
|
|
|
return (
|
|
<div className="max-w-5xl mx-auto">
|
|
<h2 className="text-lg font-semibold text-white tracking-tight mb-5">Projection Definitions</h2>
|
|
{data.length === 0 ? (
|
|
<EmptyState message="No projection definitions found" />
|
|
) : (
|
|
<div className="space-y-4">
|
|
{data.map((def, i) => (
|
|
<div
|
|
key={i}
|
|
className="bg-surface-1 rounded-xl p-5 border border-white/[0.06] hover:border-white/[0.1] transition-colors"
|
|
>
|
|
<div className="flex items-start justify-between mb-3">
|
|
<div>
|
|
<h3 className="font-mono text-lg text-gray-100">{def.name}</h3>
|
|
<div className="mt-1">
|
|
<HashBadge hash={def.hash || 'unknown'} short={false} />
|
|
</div>
|
|
</div>
|
|
<button
|
|
onClick={() => toggleExpand(def.hash || i)}
|
|
className="text-blue-400 hover:text-blue-300 text-sm font-medium transition-colors"
|
|
>
|
|
{expanded.has(def.hash || i) ? 'Hide Details' : 'Show Details'}
|
|
</button>
|
|
</div>
|
|
{expanded.has(def.hash || i) && (
|
|
<div className="mt-4 space-y-3 text-sm bg-surface-3/30 rounded-lg p-4 border border-gray-700/50">
|
|
{def.sources && (
|
|
<div>
|
|
<span className="text-gray-400 font-medium">Sources:</span>
|
|
<div className="mt-2 space-y-3">
|
|
{def.sources.map((s: any, i: number) => (
|
|
<div key={i} className="pl-3 border-l-2 border-gray-600">
|
|
<div className="text-gray-200 font-mono text-xs">{s.event_def_hash}</div>
|
|
<div className="mt-1">
|
|
<span className="text-gray-500 text-xs">bindings:</span>{' '}
|
|
<span className="text-yellow-400 text-xs">{JSON.stringify(s.bindings)}</span>
|
|
</div>
|
|
<div className="mt-0.5">
|
|
<span className="text-gray-500 text-xs">expression:</span>{' '}
|
|
<code className="text-green-400 text-xs">{s.expression}</code>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
{def.params && (
|
|
<div>
|
|
<span className="text-gray-400 font-medium">Params:</span>
|
|
<pre className="mt-1.5 p-3 bg-gray-950 rounded-lg text-xs overflow-x-auto border border-white/[0.06]">
|
|
{JSON.stringify(def.params, null, 2)
|
|
.split('\n')
|
|
.map((line, i) => {
|
|
if (line.includes(':')) {
|
|
const [key, ...rest] = line.split(':')
|
|
return (
|
|
<div key={i}>
|
|
<span className="text-blue-400">{key}:</span>
|
|
<span className="text-green-300">{rest.join(':')}</span>
|
|
</div>
|
|
)
|
|
}
|
|
return (
|
|
<div key={i} className="text-gray-300">
|
|
{line}
|
|
</div>
|
|
)
|
|
})}
|
|
</pre>
|
|
</div>
|
|
)}
|
|
{def.value_schema && (
|
|
<div>
|
|
<span className="text-gray-400 font-medium">Value Schema:</span>
|
|
<pre className="mt-1.5 p-3 bg-gray-950 rounded-lg text-xs overflow-x-auto border border-white/[0.06] text-purple-300">
|
|
{JSON.stringify(def.value_schema, null, 2)}
|
|
</pre>
|
|
</div>
|
|
)}
|
|
{def.initial_value !== undefined && (
|
|
<div>
|
|
<span className="text-gray-400 font-medium">Initial Value:</span>
|
|
<pre className="mt-1.5 p-3 bg-gray-950 rounded-lg text-xs overflow-x-auto border border-white/[0.06] text-blue-300">
|
|
{JSON.stringify(def.initial_value, null, 2)}
|
|
</pre>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|