Files
tessera/web/src/components/widgets/grouped-counts-widget.tsx
Gjermund Høsøien Wiggen b70a133ea2 feat: add dashboards — tables, CRUD API, widget data endpoint
- 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>
2026-06-09 11:26:22 +02:00

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>
);
}