'use client' import * as React from 'react' import { PenEditIcon, UserIcon, UserPlusIcon } from '../icons-v2-generated' import { cn } from '../../utils/cn' import { Autocomplete, type AutocompleteOption } from './autocomplete' import { DeletedUserAvatar } from './deleted-user-avatar' import { SearchableSelect, type SearchableSelectOption } from './searchable-select' import { SquareAvatar } from './square-avatar' export interface TicketAssigneeOption { value: string label: string imageUrl?: string } export interface AssigneeDropdownProps { currentAssignee?: { id: string name: string avatarSrc?: string /** Deleted account (DELETED / SELF_DELETED) — renders the red user-x placeholder + red name. */ deleted?: boolean } options: TicketAssigneeOption[] isLoading?: boolean isPending?: boolean onAssign: (userId: string | null) => void variant?: 'default' | 'compact' className?: string /** * Intercepts the trigger: when set (compact variant), clicking the * avatar/assign button calls this instead of opening the dropdown — e.g. to * open a Take Over confirmation for tickets still worked by the AI. */ onTriggerClick?: () => void } export function AssigneeDropdown(props: AssigneeDropdownProps) { if (props.variant === 'compact') { return } return } function CompactAssigneeDropdown({ currentAssignee, options, isLoading, onAssign, className, onTriggerClick, }: AssigneeDropdownProps) { const hasAssignee = !!currentAssignee // Current assignee first; SearchableSelect filters but never reorders. const orderedOptions = React.useMemo(() => { const withAvatars = options.map(o => ({ value: o.value, label: o.label, icon: ( ), })) if (!currentAssignee) return withAvatars const current = withAvatars.find(o => o.value === currentAssignee.id) if (!current) return withAvatars return [current, ...withAvatars.filter(o => o.value !== currentAssignee.id)] }, [options, currentAssignee]) const handleSelect = (userId: string) => { // Selecting the current assignee again unassigns. onAssign(currentAssignee?.id === userId ? null : userId) } // Board cards are links: an intercepted trigger must not navigate or bubble. const handleTriggerClick = onTriggerClick ? (event: React.MouseEvent) => { event.preventDefault() event.stopPropagation() onTriggerClick() } : undefined const trigger = hasAssignee ? ( ) : ( ) // Intercepted trigger: render the plain button — no dropdown to open. if (onTriggerClick) return trigger return ( ) } function DefaultAssigneeDropdown({ currentAssignee, options, isLoading, onAssign, className, }: AssigneeDropdownProps) { const [isEditing, setIsEditing] = React.useState(false) const hasAssignee = !!currentAssignee const renderOption = React.useCallback((option: AutocompleteOption) => { const opt = option as TicketAssigneeOption return (
{opt.label}
) }, []) if (isEditing) { return (
{ onAssign(val) setIsEditing(false) }} placeholder="Search users..." loading={isLoading} showChevron={false} startAdornment={ hasAssignee ? ( currentAssignee!.deleted ? ( ) : ( ) ) : ( ) } renderOption={renderOption} /> Assigned
) } if (hasAssignee) { return (
{currentAssignee!.deleted ? ( ) : ( )}
Assigned
) } return (
Assigned
) }