- 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>
60 lines
2.2 KiB
TypeScript
60 lines
2.2 KiB
TypeScript
"use client";
|
|
|
|
import Link from "next/link";
|
|
import { formatDistanceToNow } from "date-fns";
|
|
import { CircleIcon } from "lucide-react";
|
|
import type { WidgetData } from "@/lib/types";
|
|
import { cn, formatTicketId } from "@/lib/utils";
|
|
|
|
const STATUS_COLORS: Record<string, string> = {
|
|
new: "#64748b",
|
|
open: "#2563eb",
|
|
in_progress: "#d97706",
|
|
resolved: "#16a34a",
|
|
closed: "#71717a",
|
|
};
|
|
|
|
function statusLabel(status: string) {
|
|
return status.replaceAll("_", " ");
|
|
}
|
|
|
|
export function TicketListWidget({ data }: { data: WidgetData }) {
|
|
const tickets = data.tickets ?? [];
|
|
|
|
return (
|
|
<div className="flex h-full flex-col overflow-hidden rounded-lg border border-border bg-card">
|
|
<div className="flex items-center justify-between border-b border-border px-3 py-2">
|
|
<span className="text-xs font-semibold text-foreground">{data.title}</span>
|
|
<span className="text-[11px] tabular-nums text-muted-foreground">{data.total}</span>
|
|
</div>
|
|
<div className="flex-1 overflow-auto">
|
|
{tickets.length === 0 ? (
|
|
<p className="px-3 py-4 text-center text-xs text-muted-foreground">No tickets</p>
|
|
) : (
|
|
tickets.map((ticket) => (
|
|
<Link
|
|
key={ticket.id}
|
|
href={`/tickets/${ticket.id}`}
|
|
className="flex items-center gap-2 border-b border-border/50 px-3 py-2 text-xs transition-colors hover:bg-accent/40 last:border-b-0"
|
|
>
|
|
<CircleIcon
|
|
className="h-2 w-2 shrink-0"
|
|
style={{ color: STATUS_COLORS[ticket.status] ?? "#71717a", fill: STATUS_COLORS[ticket.status] ?? "#71717a" }}
|
|
/>
|
|
<span className="min-w-0 flex-1 truncate font-medium text-foreground">
|
|
{ticket.subject}
|
|
</span>
|
|
<span className="shrink-0 text-[11px] text-muted-foreground">
|
|
{ticket.owner_name ?? "unassigned"}
|
|
</span>
|
|
<span className="shrink-0 text-[11px] text-muted-foreground/60">
|
|
{formatDistanceToNow(new Date(ticket.updated_at), { addSuffix: true })}
|
|
</span>
|
|
</Link>
|
|
))
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|