feat: seed dashboard, fix My tickets filter
- Add demo dashboard with 7 widgets to seed script - Dashboard is_default=true — appears as home page on fresh seed - Add views/dashboards/dashboardWidgets to seed reset - Fix My tickets: now filters by first non-system user (not any owner) - Pass owner param in sidebar My tickets link - Update page.tsx view=my to respect owner URL param - Scrip engine already sorts by sort_order (verified) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,9 @@ import {
|
|||||||
templates,
|
templates,
|
||||||
tickets,
|
tickets,
|
||||||
transactions,
|
transactions,
|
||||||
|
views,
|
||||||
|
dashboards,
|
||||||
|
dashboardWidgets,
|
||||||
users,
|
users,
|
||||||
} from './schema.ts';
|
} from './schema.ts';
|
||||||
|
|
||||||
@@ -314,6 +317,9 @@ async function resetDatabase(db: Db) {
|
|||||||
await db.delete(customFieldValues);
|
await db.delete(customFieldValues);
|
||||||
await db.delete(transactions);
|
await db.delete(transactions);
|
||||||
await db.delete(queueCustomFields);
|
await db.delete(queueCustomFields);
|
||||||
|
await db.delete(dashboardWidgets);
|
||||||
|
await db.delete(dashboards);
|
||||||
|
await db.delete(views);
|
||||||
await db.delete(scrips);
|
await db.delete(scrips);
|
||||||
await db.delete(templates);
|
await db.delete(templates);
|
||||||
await db.delete(tickets);
|
await db.delete(tickets);
|
||||||
@@ -775,6 +781,56 @@ async function main() {
|
|||||||
})));
|
})));
|
||||||
|
|
||||||
console.log(`${reset ? 'Reset and seeded' : 'Seeded'} ${demoTickets.length} demo tickets across 4 queues`);
|
console.log(`${reset ? 'Reset and seeded' : 'Seeded'} ${demoTickets.length} demo tickets across 4 queues`);
|
||||||
|
|
||||||
|
// ── Dashboard seeding ──
|
||||||
|
const dashboardViews = [
|
||||||
|
{ name: 'Open tickets', filters: [{ field: 'status', operator: 'is', value: 'open' }] },
|
||||||
|
{ name: 'My tickets', filters: [{ field: 'owner', operator: 'is', value: userIds.dispatcher }] },
|
||||||
|
{ name: 'Unassigned', filters: [{ field: 'owner', operator: 'is', value: 'unassigned' }] },
|
||||||
|
{ name: 'All tickets', filters: [] },
|
||||||
|
];
|
||||||
|
|
||||||
|
const viewRecords: Record<string, string> = {};
|
||||||
|
for (const v of dashboardViews) {
|
||||||
|
const [row] = await db.insert(views).values({
|
||||||
|
name: v.name,
|
||||||
|
filters: v.filters,
|
||||||
|
is_public: true,
|
||||||
|
}).returning();
|
||||||
|
if (row) viewRecords[v.name] = row.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [dashboard] = await db.insert(dashboards).values({
|
||||||
|
name: 'Support overview',
|
||||||
|
description: 'Daily support team dashboard',
|
||||||
|
is_default: true,
|
||||||
|
}).returning();
|
||||||
|
|
||||||
|
if (dashboard) {
|
||||||
|
const widgetDefs = [
|
||||||
|
{ view: 'Open tickets', type: 'count', title: 'Open tickets', x: 0, y: 0, w: 3, h: 1 },
|
||||||
|
{ view: 'My tickets', type: 'count', title: 'My tickets', x: 3, y: 0, w: 3, h: 1 },
|
||||||
|
{ view: 'Unassigned', type: 'count', title: 'Unassigned', x: 6, y: 0, w: 3, h: 1 },
|
||||||
|
{ view: 'All tickets', type: 'count', title: 'Total tickets', x: 9, y: 0, w: 3, h: 1 },
|
||||||
|
{ view: 'Open tickets', type: 'status_chart', title: 'Status breakdown', x: 0, y: 1, w: 4, h: 2 },
|
||||||
|
{ view: 'Open tickets', type: 'ticket_list', title: 'Recent open', x: 4, y: 1, w: 5, h: 2, config: { limit: 5 } },
|
||||||
|
{ view: 'All tickets', type: 'grouped_counts', title: 'By queue', x: 9, y: 1, w: 3, h: 2, config: { group_by: 'queue' } },
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const w of widgetDefs) {
|
||||||
|
await db.insert(dashboardWidgets).values({
|
||||||
|
dashboard_id: dashboard.id,
|
||||||
|
view_id: viewRecords[w.view],
|
||||||
|
title: w.title,
|
||||||
|
widget_type: w.type,
|
||||||
|
position: { x: w.x, y: w.y, w: w.w, h: w.h },
|
||||||
|
config: w.config ?? {},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Seeded dashboard "${dashboard.name}" with ${widgetDefs.length} widgets`);
|
||||||
|
}
|
||||||
|
|
||||||
console.log('Demo data ready');
|
console.log('Demo data ready');
|
||||||
} finally {
|
} finally {
|
||||||
await pool.end();
|
await pool.end();
|
||||||
|
|||||||
@@ -409,7 +409,11 @@ function TicketWorkbenchContent() {
|
|||||||
|
|
||||||
return tickets
|
return tickets
|
||||||
.filter((ticket) => {
|
.filter((ticket) => {
|
||||||
if (view === "my" && !ticket.owner_id) return false;
|
if (view === "my") {
|
||||||
|
const myOwner = searchParams.get("owner");
|
||||||
|
if (myOwner && ticket.owner_id !== myOwner) return false;
|
||||||
|
if (!myOwner && !ticket.owner_id) return false;
|
||||||
|
}
|
||||||
if (view === "unassigned" && ticket.owner_id) return false;
|
if (view === "unassigned" && ticket.owner_id) return false;
|
||||||
if (view === "recent") {
|
if (view === "recent") {
|
||||||
const week = 7 * 24 * 60 * 60 * 1000;
|
const week = 7 * 24 * 60 * 60 * 1000;
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ import {
|
|||||||
PanelLeftIcon,
|
PanelLeftIcon,
|
||||||
CommandIcon,
|
CommandIcon,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { getTickets, getQueues, getViews, getDashboards } from "@/lib/api";
|
import { getTickets, getQueues, getViews, getDashboards, getUsers } from "@/lib/api";
|
||||||
import type { Dashboard, Queue, SavedView } from "@/lib/types";
|
import type { Dashboard, Queue, SavedView, User } from "@/lib/types";
|
||||||
import { CommandPalette } from "@/components/command-palette";
|
import { CommandPalette } from "@/components/command-palette";
|
||||||
import { ThemeToggle } from "@/components/theme-toggle";
|
import { ThemeToggle } from "@/components/theme-toggle";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
@@ -88,15 +88,23 @@ function SidebarNav() {
|
|||||||
const [queues, setQueues] = useState<(Queue & { count: number })[]>([]);
|
const [queues, setQueues] = useState<(Queue & { count: number })[]>([]);
|
||||||
const [savedViews, setSavedViews] = useState<SavedView[]>([]);
|
const [savedViews, setSavedViews] = useState<SavedView[]>([]);
|
||||||
const [dashboards, setDashboards] = useState<Dashboard[]>([]);
|
const [dashboards, setDashboards] = useState<Dashboard[]>([]);
|
||||||
|
const [currentUserId, setCurrentUserId] = useState<string | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getTickets().then(({ data }) => {
|
// Find current user and compute view counts
|
||||||
|
Promise.all([getTickets(), getUsers()]).then(([ticketRes, userRes]) => {
|
||||||
|
const data = ticketRes.data;
|
||||||
|
const users = userRes.data ?? [];
|
||||||
|
const currentUser = users.find((u) => u.username !== 'system') ?? users[0] ?? null;
|
||||||
|
if (currentUser) setCurrentUserId(currentUser.id);
|
||||||
|
|
||||||
if (data) {
|
if (data) {
|
||||||
|
const myId = currentUser?.id;
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const week = 7 * 24 * 60 * 60 * 1000;
|
const week = 7 * 24 * 60 * 60 * 1000;
|
||||||
setCounts({
|
setCounts({
|
||||||
all: data.length,
|
all: data.length,
|
||||||
my: data.filter((t) => t.owner_id).length,
|
my: myId ? data.filter((t) => t.owner_id === myId).length : 0,
|
||||||
unassigned: data.filter((t) => !t.owner_id).length,
|
unassigned: data.filter((t) => !t.owner_id).length,
|
||||||
recent: data.filter(
|
recent: data.filter(
|
||||||
(t) => new Date(t.updated_at).getTime() > now - week
|
(t) => new Date(t.updated_at).getTime() > now - week
|
||||||
@@ -139,7 +147,7 @@ function SidebarNav() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "My tickets",
|
label: "My tickets",
|
||||||
href: "/?view=my",
|
href: currentUserId ? `/?view=my&owner=${currentUserId}` : "/?view=my",
|
||||||
param: "my",
|
param: "my",
|
||||||
count: counts.my,
|
count: counts.my,
|
||||||
icon: UserIcon,
|
icon: UserIcon,
|
||||||
|
|||||||
Reference in New Issue
Block a user