mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-27 04:56:20 +02:00
fix(runtime): redesign filter bar with segmented control and owner dropdown
Replace cluttered inline owner pills with a clean two-part filter bar: - Left: Mine/All segmented control with proper bg-muted container - Right: Owner DropdownMenu (only in All mode) with avatars and counts Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,15 @@
|
||||
import { Server, ArrowUpCircle } from "lucide-react";
|
||||
import { Server, ArrowUpCircle, ChevronDown, Check } from "lucide-react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import type { AgentRuntime, MemberWithUser } from "@multica/core/types";
|
||||
import { useWorkspaceId } from "@multica/core/hooks";
|
||||
import { memberListOptions } from "@multica/core/workspace/queries";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
} from "@multica/ui/components/ui/dropdown-menu";
|
||||
import { ActorAvatar } from "../../common/actor-avatar";
|
||||
import { RuntimeModeIcon } from "./shared";
|
||||
|
||||
@@ -102,11 +109,19 @@ export function RuntimeList({
|
||||
.filter(Boolean) as MemberWithUser[]
|
||||
: [];
|
||||
|
||||
// Count runtimes per owner
|
||||
const ownerCounts = new Map<string, number>();
|
||||
for (const r of runtimes) {
|
||||
if (r.owner_id) ownerCounts.set(r.owner_id, (ownerCounts.get(r.owner_id) ?? 0) + 1);
|
||||
}
|
||||
|
||||
// Apply client-side owner filter when in "all" mode
|
||||
const filteredRuntimes = filter === "all" && ownerFilter
|
||||
? runtimes.filter((r) => r.owner_id === ownerFilter)
|
||||
: runtimes;
|
||||
|
||||
const selectedOwner = ownerFilter ? getOwnerMember(ownerFilter) : null;
|
||||
|
||||
return (
|
||||
<div className="overflow-y-auto h-full border-r">
|
||||
<div className="flex h-12 items-center justify-between border-b px-4">
|
||||
@@ -117,53 +132,75 @@ export function RuntimeList({
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Filter toggle */}
|
||||
<div className="flex items-center gap-1 border-b px-4 py-2">
|
||||
<button
|
||||
onClick={() => { onFilterChange("mine"); onOwnerFilterChange(null); }}
|
||||
className={`rounded-md px-2.5 py-1 text-xs font-medium transition-colors ${
|
||||
filter === "mine"
|
||||
? "bg-accent text-accent-foreground"
|
||||
: "text-muted-foreground hover:text-foreground"
|
||||
}`}
|
||||
>
|
||||
Mine
|
||||
</button>
|
||||
<button
|
||||
onClick={() => { onFilterChange("all"); onOwnerFilterChange(null); }}
|
||||
className={`rounded-md px-2.5 py-1 text-xs font-medium transition-colors ${
|
||||
filter === "all" && !ownerFilter
|
||||
? "bg-accent text-accent-foreground"
|
||||
: "text-muted-foreground hover:text-foreground"
|
||||
}`}
|
||||
>
|
||||
All
|
||||
</button>
|
||||
{/* Filter bar */}
|
||||
<div className="flex items-center justify-between border-b px-4 py-2">
|
||||
{/* Scope toggle */}
|
||||
<div className="flex items-center gap-0.5 rounded-md bg-muted p-0.5">
|
||||
<button
|
||||
onClick={() => { onFilterChange("mine"); onOwnerFilterChange(null); }}
|
||||
className={`rounded px-2.5 py-1 text-xs font-medium transition-colors ${
|
||||
filter === "mine"
|
||||
? "bg-background text-foreground shadow-sm"
|
||||
: "text-muted-foreground hover:text-foreground"
|
||||
}`}
|
||||
>
|
||||
Mine
|
||||
</button>
|
||||
<button
|
||||
onClick={() => { onFilterChange("all"); onOwnerFilterChange(null); }}
|
||||
className={`rounded px-2.5 py-1 text-xs font-medium transition-colors ${
|
||||
filter === "all"
|
||||
? "bg-background text-foreground shadow-sm"
|
||||
: "text-muted-foreground hover:text-foreground"
|
||||
}`}
|
||||
>
|
||||
All
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Owner filter pills (shown in "all" mode when there are multiple owners) */}
|
||||
{/* Owner dropdown (only in All mode with multiple owners) */}
|
||||
{filter === "all" && uniqueOwners.length > 1 && (
|
||||
<>
|
||||
<div className="mx-1 h-4 w-px bg-border" />
|
||||
{uniqueOwners.map((m) => (
|
||||
<button
|
||||
key={m.user_id}
|
||||
onClick={() => onOwnerFilterChange(ownerFilter === m.user_id ? null : m.user_id)}
|
||||
className={`flex items-center gap-1 rounded-md px-2 py-1 text-xs font-medium transition-colors ${
|
||||
ownerFilter === m.user_id
|
||||
? "bg-accent text-accent-foreground"
|
||||
: "text-muted-foreground hover:text-foreground"
|
||||
}`}
|
||||
title={m.name}
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger
|
||||
render={
|
||||
<button className="flex items-center gap-1.5 rounded-md px-2 py-1 text-xs font-medium text-muted-foreground transition-colors hover:text-foreground hover:bg-accent" />
|
||||
}
|
||||
>
|
||||
{selectedOwner ? (
|
||||
<>
|
||||
<ActorAvatar actorType="member" actorId={selectedOwner.user_id} size={16} />
|
||||
<span className="max-w-20 truncate">{selectedOwner.name}</span>
|
||||
</>
|
||||
) : (
|
||||
<span>Owner</span>
|
||||
)}
|
||||
<ChevronDown className="h-3 w-3 opacity-50" />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-48">
|
||||
<DropdownMenuItem
|
||||
onClick={() => onOwnerFilterChange(null)}
|
||||
className="flex items-center justify-between"
|
||||
>
|
||||
<ActorAvatar
|
||||
actorType="member"
|
||||
actorId={m.user_id}
|
||||
size={14}
|
||||
/>
|
||||
<span className="max-w-16 truncate">{m.name}</span>
|
||||
</button>
|
||||
))}
|
||||
</>
|
||||
<span className="text-xs">All owners</span>
|
||||
{!ownerFilter && <Check className="h-3.5 w-3.5 text-foreground" />}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
{uniqueOwners.map((m) => (
|
||||
<DropdownMenuItem
|
||||
key={m.user_id}
|
||||
onClick={() => onOwnerFilterChange(ownerFilter === m.user_id ? null : m.user_id)}
|
||||
className="flex items-center justify-between"
|
||||
>
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
<ActorAvatar actorType="member" actorId={m.user_id} size={18} />
|
||||
<span className="text-xs truncate">{m.name}</span>
|
||||
<span className="text-xs text-muted-foreground">{ownerCounts.get(m.user_id) ?? 0}</span>
|
||||
</div>
|
||||
{ownerFilter === m.user_id && <Check className="h-3.5 w-3.5 shrink-0 text-foreground" />}
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user