Polish UI: enhance dialogs, command palette, and page layouts

Expand NewIssueDialog with richer form fields. Add NewProjectDialog.
Enhance CommandPalette with more actions and search. Improve
CompanySwitcher, EmptyState, and IssueProperties. Flesh out Activity,
Companies, Dashboard, and Inbox pages with real content and layouts.
Refine sidebar, routing, and dialog context. CSS tweaks for dark theme.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Forgotten
2026-02-17 10:53:20 -06:00
parent 102f61c96d
commit d912670f72
22 changed files with 1301 additions and 254 deletions

View File

@@ -1,6 +1,7 @@
import { useState, useEffect, useCallback } from "react";
import { useNavigate } from "react-router-dom";
import { useCompany } from "../context/CompanyContext";
import { useDialog } from "../context/DialogContext";
import { issuesApi } from "../api/issues";
import { agentsApi } from "../api/agents";
import { projectsApi } from "../api/projects";
@@ -11,8 +12,21 @@ import {
CommandInput,
CommandItem,
CommandList,
CommandSeparator,
} from "@/components/ui/command";
import { CircleDot, Bot, Hexagon, Target, LayoutDashboard, Inbox } from "lucide-react";
import {
CircleDot,
Bot,
Hexagon,
Target,
LayoutDashboard,
Inbox,
DollarSign,
History,
GitBranch,
SquarePen,
Plus,
} from "lucide-react";
import type { Issue, Agent, Project } from "@paperclip/shared";
export function CommandPalette() {
@@ -22,6 +36,7 @@ export function CommandPalette() {
const [projects, setProjects] = useState<Project[]>([]);
const navigate = useNavigate();
const { selectedCompanyId } = useCompany();
const { openNewIssue } = useDialog();
useEffect(() => {
function handleKeyDown(e: KeyboardEvent) {
@@ -57,6 +72,11 @@ export function CommandPalette() {
navigate(path);
}
const agentName = (id: string | null) => {
if (!id) return null;
return agents.find((a) => a.id === id)?.name ?? null;
};
return (
<CommandDialog open={open} onOpenChange={setOpen}>
<CommandInput placeholder="Search issues, agents, projects..." />
@@ -64,7 +84,7 @@ export function CommandPalette() {
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup heading="Pages">
<CommandItem onSelect={() => go("/")}>
<CommandItem onSelect={() => go("/dashboard")}>
<LayoutDashboard className="mr-2 h-4 w-4" />
Dashboard
</CommandItem>
@@ -72,7 +92,7 @@ export function CommandPalette() {
<Inbox className="mr-2 h-4 w-4" />
Inbox
</CommandItem>
<CommandItem onSelect={() => go("/tasks")}>
<CommandItem onSelect={() => go("/issues")}>
<CircleDot className="mr-2 h-4 w-4" />
Issues
</CommandItem>
@@ -88,42 +108,92 @@ export function CommandPalette() {
<Bot className="mr-2 h-4 w-4" />
Agents
</CommandItem>
<CommandItem onSelect={() => go("/costs")}>
<DollarSign className="mr-2 h-4 w-4" />
Costs
</CommandItem>
<CommandItem onSelect={() => go("/activity")}>
<History className="mr-2 h-4 w-4" />
Activity
</CommandItem>
<CommandItem onSelect={() => go("/org")}>
<GitBranch className="mr-2 h-4 w-4" />
Org Chart
</CommandItem>
</CommandGroup>
<CommandSeparator />
<CommandGroup heading="Actions">
<CommandItem
onSelect={() => {
setOpen(false);
openNewIssue();
}}
>
<SquarePen className="mr-2 h-4 w-4" />
Create new issue
<span className="ml-auto text-xs text-muted-foreground">C</span>
</CommandItem>
<CommandItem onSelect={() => go("/agents")}>
<Plus className="mr-2 h-4 w-4" />
Create new agent
</CommandItem>
<CommandItem onSelect={() => go("/projects")}>
<Plus className="mr-2 h-4 w-4" />
Create new project
</CommandItem>
</CommandGroup>
{issues.length > 0 && (
<CommandGroup heading="Issues">
{issues.slice(0, 10).map((issue) => (
<CommandItem key={issue.id} onSelect={() => go(`/issues/${issue.id}`)}>
<CircleDot className="mr-2 h-4 w-4" />
<span className="text-muted-foreground mr-2 font-mono text-xs">
{issue.id.slice(0, 8)}
</span>
{issue.title}
</CommandItem>
))}
</CommandGroup>
<>
<CommandSeparator />
<CommandGroup heading="Issues">
{issues.slice(0, 10).map((issue) => (
<CommandItem key={issue.id} onSelect={() => go(`/issues/${issue.id}`)}>
<CircleDot className="mr-2 h-4 w-4" />
<span className="text-muted-foreground mr-2 font-mono text-xs">
{issue.id.slice(0, 8)}
</span>
<span className="flex-1 truncate">{issue.title}</span>
{issue.assigneeAgentId && (
<span className="text-xs text-muted-foreground ml-2">
{agentName(issue.assigneeAgentId)}
</span>
)}
</CommandItem>
))}
</CommandGroup>
</>
)}
{agents.length > 0 && (
<CommandGroup heading="Agents">
{agents.slice(0, 10).map((agent) => (
<CommandItem key={agent.id} onSelect={() => go(`/agents/${agent.id}`)}>
<Bot className="mr-2 h-4 w-4" />
{agent.name}
</CommandItem>
))}
</CommandGroup>
<>
<CommandSeparator />
<CommandGroup heading="Agents">
{agents.slice(0, 10).map((agent) => (
<CommandItem key={agent.id} onSelect={() => go(`/agents/${agent.id}`)}>
<Bot className="mr-2 h-4 w-4" />
{agent.name}
<span className="text-xs text-muted-foreground ml-2">{agent.role}</span>
</CommandItem>
))}
</CommandGroup>
</>
)}
{projects.length > 0 && (
<CommandGroup heading="Projects">
{projects.slice(0, 10).map((project) => (
<CommandItem key={project.id} onSelect={() => go(`/projects/${project.id}`)}>
<Hexagon className="mr-2 h-4 w-4" />
{project.name}
</CommandItem>
))}
</CommandGroup>
<>
<CommandSeparator />
<CommandGroup heading="Projects">
{projects.slice(0, 10).map((project) => (
<CommandItem key={project.id} onSelect={() => go(`/projects/${project.id}`)}>
<Hexagon className="mr-2 h-4 w-4" />
{project.name}
</CommandItem>
))}
</CommandGroup>
</>
)}
</CommandList>
</CommandDialog>