diff --git a/web/src/app/page.tsx b/web/src/app/page.tsx index 592a6d4..3a3a7c5 100644 --- a/web/src/app/page.tsx +++ b/web/src/app/page.tsx @@ -1,6 +1,7 @@ "use client"; import { Suspense, useCallback, useEffect, useMemo, useState } from "react"; +import { createPortal } from "react-dom"; import { useRouter, useSearchParams } from "next/navigation"; import { ArrowDownAZIcon, @@ -677,185 +678,6 @@ function TicketWorkbenchContent() { Add filter - {addFilterOpen && ( - <> -
{ - setAddFilterOpen(false); - setAddFilterField(null); - }} - /> -
- {!addFilterField ? ( - /* Step 1: choose field */ - <> - - - {customFields.map((cf) => ( - - ))} - - ) : ( - /* Step 2: operator + value */ -
-
- - - {addFilterField.startsWith("cf.") ? addFilterField.slice(3) : addFilterField} - -
- - {addFilterField === "queue" ? ( - - ) : addFilterField === "owner" ? ( - - ) : ( - setAddFilterValue(e.target.value)} - placeholder="Value" - className="h-7 w-full rounded border border-input bg-card px-2 text-xs outline-none" - onKeyDown={(e) => { - if (e.key === "Enter" && addFilterValue.trim()) { - const field = addFilterField; - const value = addFilterValue; - let valueLabel = value; - setFilters((prev) => [...prev, { - id: crypto.randomUUID(), - field, - operator: addFilterOperator, - value, - label: buildFilterLabel(field, addFilterOperator, valueLabel), - }]); - setAddFilterField(null); - setAddFilterOpen(false); - } - }} - /> - )} -
- - -
-
- )} -
- - )}
@@ -1217,6 +1039,116 @@ function TicketWorkbenchContent() { + + {typeof document !== "undefined" && addFilterOpen && createPortal( + <> +
{ + setAddFilterOpen(false); + setAddFilterField(null); + }} + /> +
+ {!addFilterField ? ( + <> + + + {customFields.map((cf) => ( + + ))} + + ) : ( +
+
+ + {addFilterField.startsWith("cf.") ? addFilterField.slice(3) : addFilterField} +
+ + {addFilterField === "queue" ? ( + + ) : addFilterField === "owner" ? ( + + ) : ( + setAddFilterValue(e.target.value)} placeholder="Value" className="h-7 w-full rounded border border-input bg-card px-2 text-xs outline-none" + onKeyDown={(e) => { + if (e.key === "Enter" && addFilterValue.trim()) { + setFilters((prev) => [...prev, { id: crypto.randomUUID(), field: addFilterField, operator: addFilterOperator, value: addFilterValue, label: buildFilterLabel(addFilterField, addFilterOperator, addFilterValue) }]); + setAddFilterField(null); + setAddFilterOpen(false); + } + }} + /> + )} +
+ + +
+
+ )} +
+ , + document.body + )}
); } diff --git a/web/src/components/app-shell.tsx b/web/src/components/app-shell.tsx index 7375da1..f68d43e 100644 --- a/web/src/components/app-shell.tsx +++ b/web/src/components/app-shell.tsx @@ -6,6 +6,7 @@ import Link from "next/link"; import { ChevronRightIcon, LayoutGridIcon, + PlusIcon, UserIcon, InboxIcon, ClockIcon, @@ -14,7 +15,7 @@ import { PanelLeftIcon, CommandIcon, } from "lucide-react"; -import { getTickets, getQueues, getViews, getDashboards, getUsers } from "@/lib/api"; +import { getTickets, getQueues, getViews, getDashboards, getUsers, createDashboard } from "@/lib/api"; import type { Dashboard, Queue, SavedView, User } from "@/lib/types"; import { CommandPalette } from "@/components/command-palette"; import { ThemeToggle } from "@/components/theme-toggle"; @@ -95,6 +96,8 @@ function SidebarNav() { queues: true, views: true, }); + const [newDashboardName, setNewDashboardName] = useState(""); + const [addingDashboard, setAddingDashboard] = useState(false); useEffect(() => { // Find current user and compute view counts @@ -199,16 +202,52 @@ function SidebarNav() { {dashboards.length > 0 && (
{!collapsed && ( - +
+ + {addingDashboard ? ( +
+ setNewDashboardName(e.target.value)} + placeholder="Name" + className="h-6 w-24 rounded border border-sidebar-border bg-sidebar-accent px-1.5 text-[11px] text-sidebar-foreground outline-none" + autoFocus + onKeyDown={async (e) => { + if (e.key === "Enter" && newDashboardName.trim()) { + const { data } = await createDashboard({ name: newDashboardName.trim(), is_default: false }); + if (data) { + setDashboards((prev) => [...prev, data]); + setNewDashboardName(""); + setAddingDashboard(false); + window.location.href = `/dashboards/${data.id}`; + } + } else if (e.key === "Escape") { + setNewDashboardName(""); + setAddingDashboard(false); + } + }} + /> +
+ ) : ( + + )} +
)} {expanded.dashboards && dashboards.map((dash) => { const active =