From 23bf369748bf76fd4453cd9e3dd17c45e9c552c0 Mon Sep 17 00:00:00 2001 From: Jiayuan Zhang Date: Fri, 10 Apr 2026 01:50:50 +0800 Subject: [PATCH] refactor(views): reuse AssigneePicker in CreateIssueModal Replace the hand-rolled inline assignee Popover in CreateIssueModal with the shared AssigneePicker component. This fixes missing features (private agent permission checks, lock icon, disabled state, selection checkmark) and ensures consistent behavior across all assignee dropdowns. --- packages/views/modals/create-issue.tsx | 122 +++---------------------- 1 file changed, 13 insertions(+), 109 deletions(-) diff --git a/packages/views/modals/create-issue.tsx b/packages/views/modals/create-issue.tsx index 991c43cabd..5786d6dcd3 100644 --- a/packages/views/modals/create-issue.tsx +++ b/packages/views/modals/create-issue.tsx @@ -2,7 +2,7 @@ import { useState, useRef } from "react"; import { useNavigation } from "../navigation"; -import { CalendarDays, Check, ChevronRight, FolderKanban, Maximize2, Minimize2, UserMinus, X as XIcon } from "lucide-react"; +import { CalendarDays, Check, ChevronRight, FolderKanban, Maximize2, Minimize2, X as XIcon } from "lucide-react"; import { cn } from "@multica/ui/lib/utils"; import { toast } from "sonner"; import type { IssueStatus, IssuePriority, IssueAssigneeType } from "@multica/core/types"; @@ -28,20 +28,17 @@ import { Tooltip, TooltipTrigger, TooltipContent } from "@multica/ui/components/ import { Button } from "@multica/ui/components/ui/button"; import { ContentEditor, type ContentEditorRef } from "../editor"; import { TitleEditor } from "../editor"; -import { StatusIcon, PriorityIcon } from "../issues/components"; +import { StatusIcon, PriorityIcon, AssigneePicker } from "../issues/components"; import { ALL_STATUSES, STATUS_CONFIG, PRIORITY_ORDER, PRIORITY_CONFIG } from "@multica/core/issues/config"; import { useWorkspaceStore } from "@multica/core/workspace"; -import { useActorName } from "@multica/core/workspace/hooks"; import { useQuery } from "@tanstack/react-query"; import { useWorkspaceId } from "@multica/core/hooks"; -import { memberListOptions, agentListOptions } from "@multica/core/workspace/queries"; import { projectListOptions } from "@multica/core/projects/queries"; import { useIssueDraftStore } from "@multica/core/issues/stores/draft-store"; import { useCreateIssue } from "@multica/core/issues/mutations"; import { useFileUpload } from "@multica/core/hooks/use-file-upload"; import { api } from "@multica/core/api"; import { FileUploadButton } from "@multica/ui/components/common/file-upload-button"; -import { ActorAvatar } from "../common/actor-avatar"; // --------------------------------------------------------------------------- // Pill trigger — shared rounded-full button style for toolbar @@ -75,10 +72,7 @@ export function CreateIssueModal({ onClose, data }: { onClose: () => void; data? const router = useNavigation(); const workspaceName = useWorkspaceStore((s) => s.workspace?.name); const wsId = useWorkspaceId(); - const { data: members = [] } = useQuery(memberListOptions(wsId)); - const { data: agents = [] } = useQuery(agentListOptions(wsId)); const { data: projects = [] } = useQuery(projectListOptions(wsId)); - const { getActorName } = useActorName(); const draft = useIssueDraftStore((s) => s.draft); const setDraft = useIssueDraftStore((s) => s.setDraft); @@ -97,10 +91,6 @@ export function CreateIssueModal({ onClose, data }: { onClose: () => void; data? ); const [isExpanded, setIsExpanded] = useState(false); - // Assignee popover - const [assigneeOpen, setAssigneeOpen] = useState(false); - const [assigneeFilter, setAssigneeFilter] = useState(""); - // Due date popover const [dueDateOpen, setDueDateOpen] = useState(false); @@ -115,15 +105,6 @@ export function CreateIssueModal({ onClose, data }: { onClose: () => void; data? return result; }; - const assigneeQuery = assigneeFilter.toLowerCase(); - const filteredMembers = members.filter((m) => m.name.toLowerCase().includes(assigneeQuery)); - const filteredAgents = agents.filter((a) => !a.archived_at && a.name.toLowerCase().includes(assigneeQuery)); - - const assigneeLabel = - assigneeType && assigneeId - ? getActorName(assigneeType, assigneeId) - : "Assignee"; - const dueDateObj = dueDate ? new Date(dueDate) : undefined; // Sync field changes to draft store @@ -312,94 +293,17 @@ export function CreateIssueModal({ onClose, data }: { onClose: () => void; data? - {/* Assignee — Popover for search support */} - { setAssigneeOpen(v); if (!v) setAssigneeFilter(""); }}> - - {assigneeType && assigneeId ? ( - <> - - {assigneeLabel} - - ) : ( - Assignee - )} - - } - /> - -
- setAssigneeFilter(e.target.value)} - placeholder="Assign to..." - className="w-full bg-transparent text-sm placeholder:text-muted-foreground outline-none" - /> -
-
- {/* Unassigned */} - - - {/* Members */} - {filteredMembers.length > 0 && ( - <> -
Members
- {filteredMembers.map((m) => ( - - ))} - - )} - - {/* Agents */} - {filteredAgents.length > 0 && ( - <> -
Agents
- {filteredAgents.map((a) => ( - - ))} - - )} - - {filteredMembers.length === 0 && filteredAgents.length === 0 && assigneeFilter && ( -
No results
- )} -
-
-
+ {/* Assignee */} + updateAssignee( + u.assignee_type ?? undefined, + u.assignee_id ?? undefined, + )} + triggerRender={} + align="start" + /> {/* Due date */}