- New dashboards table (name, description, layout, is_default) - New dashboard_widgets table (view_id, title, widget_type, position, config) - GET/POST/PATCH/DELETE /dashboards - GET/POST/PATCH/DELETE /dashboards/:id/widgets - GET /dashboards/:id/widgets/:id/data — runs saved view filters, returns pre-aggregated data for count/ticket_list/status_chart/grouped_counts - is_default uniqueness enforced on PATCH Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
40 lines
1.5 KiB
TypeScript
40 lines
1.5 KiB
TypeScript
"use client";
|
|
|
|
import type { WidgetData } from "@/lib/types";
|
|
|
|
export function GroupedCountsWidget({ data }: { data: WidgetData }) {
|
|
const groups = data.groups ?? {};
|
|
const entries = Object.entries(groups).sort(([, a], [, b]) => b - a);
|
|
const max = entries.length > 0 ? Math.max(...entries.map(([, c]) => c)) : 1;
|
|
|
|
if (entries.length === 0) {
|
|
return (
|
|
<div className="flex h-full items-center justify-center rounded-lg border border-border bg-card p-4">
|
|
<p className="text-xs text-muted-foreground">No data</p>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="flex h-full flex-col overflow-hidden rounded-lg border border-border bg-card">
|
|
<div className="border-b border-border px-3 py-2">
|
|
<span className="text-xs font-semibold text-foreground">{data.title}</span>
|
|
</div>
|
|
<div className="flex-1 space-y-1.5 overflow-auto p-3">
|
|
{entries.map(([label, count]) => (
|
|
<div key={label} className="flex items-center gap-2 text-xs">
|
|
<span className="w-20 shrink-0 truncate text-foreground">{label}</span>
|
|
<div className="flex-1">
|
|
<div
|
|
className="h-2.5 rounded-sm bg-primary/60 transition-all"
|
|
style={{ width: `${Math.round((count / max) * 100)}%` }}
|
|
/>
|
|
</div>
|
|
<span className="w-8 shrink-0 text-right tabular-nums text-muted-foreground">{count}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|