|
8 | 8 |
|
9 | 9 | import type { DashboardSchema, DashboardWidgetSchema } from '@object-ui/types'; |
10 | 10 | import { SchemaRenderer } from '@object-ui/react'; |
11 | | -import { cn } from '@object-ui/components'; |
12 | | -import { forwardRef } from 'react'; |
| 11 | +import { cn, Card, CardHeader, CardTitle, CardContent } from '@object-ui/components'; |
| 12 | +import { forwardRef, useMemo } from 'react'; |
| 13 | + |
| 14 | +// Color palette for charts |
| 15 | +const CHART_COLORS = [ |
| 16 | + 'hsl(var(--chart-1))', |
| 17 | + 'hsl(var(--chart-2))', |
| 18 | + 'hsl(var(--chart-3))', |
| 19 | + 'hsl(var(--chart-4))', |
| 20 | + 'hsl(var(--chart-5))', |
| 21 | +]; |
13 | 22 |
|
14 | 23 | export const DashboardRenderer = forwardRef<HTMLDivElement, { schema: DashboardSchema; className?: string; [key: string]: any }>( |
15 | 24 | ({ schema, className, ...props }, ref) => { |
16 | | - const columns = schema.columns || 3; |
| 25 | + const columns = schema.columns || 4; // Default to 4 columns for better density |
17 | 26 | const gap = schema.gap || 4; |
18 | 27 |
|
19 | | - // Use style to convert gap number to pixels or use tailwind classes if possible |
20 | | - // Here using inline style for grid gap which maps to 0.25rem * 4 * gap = gap rem |
21 | | - |
22 | 28 | return ( |
23 | 29 | <div |
24 | 30 | ref={ref} |
25 | | - className={cn("grid", className)} |
| 31 | + className={cn("grid auto-rows-min", className)} |
26 | 32 | style={{ |
27 | 33 | gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))`, |
28 | 34 | gap: `${gap * 0.25}rem` |
29 | 35 | }} |
30 | 36 | {...props} |
31 | 37 | > |
32 | | - {schema.widgets?.map((widget: DashboardWidgetSchema) => ( |
33 | | - <div |
34 | | - key={widget.id} |
35 | | - className={cn("border rounded-lg p-4 bg-card text-card-foreground shadow-sm")} |
36 | | - style={widget.layout ? { |
37 | | - gridColumn: `span ${widget.layout.w}`, |
38 | | - gridRow: `span ${widget.layout.h}` |
39 | | - }: undefined} |
40 | | - > |
41 | | - {widget.title && <h3 className="font-semibold mb-2">{widget.title}</h3>} |
42 | | - <SchemaRenderer schema={widget.component} /> |
43 | | - </div> |
44 | | - ))} |
| 38 | + {schema.widgets?.map((widget: DashboardWidgetSchema) => { |
| 39 | + // Logic to determine what to render |
| 40 | + // Supports both Component Schema (widget.component) and Shorthand (widget.type) |
| 41 | + |
| 42 | + const componentSchema = useMemo(() => { |
| 43 | + if (widget.component) return widget.component; |
| 44 | + |
| 45 | + // Handle Shorthand Registry Mappings |
| 46 | + if (widget.type === 'bar' || widget.type === 'line' || widget.type === 'area' || widget.type === 'pie' || widget.type === 'donut') { |
| 47 | + // Extract data from 'data.items' or 'data' array |
| 48 | + const dataItems = Array.isArray((widget as any).data) ? (widget as any).data : (widget as any).data?.items || []; |
| 49 | + |
| 50 | + // Map xField/yField to ChartRenderer expectations |
| 51 | + const options = (widget as any).options || {}; |
| 52 | + const xAxisKey = options.xField || 'name'; |
| 53 | + const yField = options.yField || 'value'; |
| 54 | + |
| 55 | + return { |
| 56 | + type: 'chart', |
| 57 | + chartType: widget.type, |
| 58 | + data: dataItems, |
| 59 | + xAxisKey: xAxisKey, |
| 60 | + series: [{ dataKey: yField }], |
| 61 | + colors: CHART_COLORS, |
| 62 | + className: "h-[300px]" // Enforce height |
| 63 | + }; |
| 64 | + } |
| 65 | + |
| 66 | + if (widget.type === 'table') { |
| 67 | + // Map to ObjectGrid |
| 68 | + return { |
| 69 | + type: 'data-table', |
| 70 | + ...(widget as any).options, |
| 71 | + data: (widget as any).data?.items || [], |
| 72 | + searchable: false, // Simple table for dashboard |
| 73 | + pagination: false, |
| 74 | + className: "border-0" |
| 75 | + }; |
| 76 | + } |
| 77 | + |
| 78 | + return widget; // Fallback to widget itself as schema |
| 79 | + }, [widget]); |
| 80 | + |
| 81 | + return ( |
| 82 | + <Card |
| 83 | + key={widget.id || widget.title} |
| 84 | + className={cn( |
| 85 | + "overflow-hidden border-border/50 shadow-sm transition-all hover:shadow-md", |
| 86 | + "bg-card/50 backdrop-blur-sm" |
| 87 | + )} |
| 88 | + style={widget.layout ? { |
| 89 | + gridColumn: `span ${widget.layout.w}`, |
| 90 | + gridRow: `span ${widget.layout.h}` |
| 91 | + }: undefined} |
| 92 | + > |
| 93 | + {widget.title && ( |
| 94 | + <CardHeader className="pb-2 border-b border-border/40 bg-muted/20"> |
| 95 | + <CardTitle className="text-base font-medium tracking-tight truncate" title={widget.title}> |
| 96 | + {widget.title} |
| 97 | + </CardTitle> |
| 98 | + </CardHeader> |
| 99 | + )} |
| 100 | + <CardContent className="p-0"> |
| 101 | + <div className={cn("h-full w-full", !widget.title ? "p-4" : "p-4")}> |
| 102 | + <SchemaRenderer schema={componentSchema} /> |
| 103 | + </div> |
| 104 | + </CardContent> |
| 105 | + </Card> |
| 106 | + ); |
| 107 | + })} |
45 | 108 | </div> |
46 | 109 | ); |
47 | 110 | } |
|
0 commit comments