init: rapid_dashboards app from fn_registry

This commit is contained in:
dataforge
2026-04-06 00:57:13 +02:00
commit b7f354e081
46 changed files with 6139 additions and 0 deletions
+63
View File
@@ -0,0 +1,63 @@
import { useState } from 'react'
import { ChevronDown, ChevronRight } from 'lucide-react'
import { WidgetRenderer } from './WidgetRenderer'
import type { SectionDef, QueryDef } from '../types'
import type { FilterValues } from '../hooks/useFilterState'
interface SectionProps {
section: SectionDef
queries: Record<string, QueryDef>
filters: FilterValues
globalColumns: number
getData: (widgetID: string, filters: FilterValues) => Promise<Record<string, unknown>[]>
}
export function Section({ section, queries, filters, globalColumns, getData }: SectionProps) {
const [collapsed, setCollapsed] = useState(false)
const columns = section.columns || globalColumns
return (
<div className="space-y-2">
<button
className="flex items-center gap-2 cursor-pointer select-none bg-transparent border-none p-0"
onClick={() => section.collapsible && setCollapsed(c => !c)}
type="button"
>
{section.collapsible && (
collapsed
? <ChevronRight className="w-4 h-4 text-[var(--muted-foreground)]" />
: <ChevronDown className="w-4 h-4 text-[var(--muted-foreground)]" />
)}
<h2 className="text-sm font-medium text-[var(--muted-foreground)] uppercase tracking-wider">
{section.title}
</h2>
</button>
{!collapsed && (
<div
className="grid gap-2"
style={{
gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))`,
}}
>
{section.widgets.map(widget => (
<div
key={widget.id}
style={{
gridColumn: `span ${Math.min(widget.span || 1, columns)}`,
gridRow: widget.rowSpan ? `span ${widget.rowSpan}` : undefined,
}}
>
<WidgetRenderer
widget={widget}
queryDef={queries[widget.query]}
filters={filters}
getData={getData}
/>
</div>
))}
</div>
)}
</div>
)
}