import { useEffect, useMemo, useRef, useState } from "react"; import { useQuery } from "@tanstack/react-query"; import { ChevronDown, Search, Check, X } from "lucide-react"; import { getDirectory } from "@/lib/api"; import { classNames } from "@/lib/format"; export type MemberOption = { value: string; label: string; sub?: string }; // 구성원 검색·선택 콤보박스. 이메일을 직접 타이핑하는 대신 등록된 구성원을 // 이름/이메일로 검색해 드롭다운에서 고른다. // - options 미지정 시 전체 구성원 디렉터리(getMembers)를 불러온다(관리자 컨텍스트). // - options 지정 시 그 목록만 사용(예: 작업 담당자 = 프로젝트 작업자). export function MemberSelect({ value, onChange, options, placeholder = "구성원 검색·선택", disabled, allowClear = true, dropUp = false, }: { value: string; onChange: (v: string) => void; options?: MemberOption[]; placeholder?: string; disabled?: boolean; allowClear?: boolean; dropUp?: boolean; }) { const [open, setOpen] = useState(false); const [q, setQ] = useState(""); const ref = useRef(null); const inputRef = useRef(null); const memQ = useQuery({ queryKey: ["directory"], queryFn: getDirectory, enabled: !options }); const opts: MemberOption[] = options ?? (memQ.data ?? []).map((m) => ({ value: m.email, label: m.displayName || m.email.split("@")[0], sub: m.email, })); const selected = opts.find((o) => o.value === value); useEffect(() => { if (!open) return; const onDoc = (e: MouseEvent) => { if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false); }; document.addEventListener("mousedown", onDoc); return () => document.removeEventListener("mousedown", onDoc); }, [open]); useEffect(() => { if (open) setTimeout(() => inputRef.current?.focus(), 0); }, [open]); const filtered = useMemo(() => { const t = q.trim().toLowerCase(); if (!t) return opts; return opts.filter((o) => `${o.label} ${o.sub ?? ""} ${o.value}`.toLowerCase().includes(t)); }, [opts, q]); return (
{open && !disabled && (
setQ(e.target.value)} />
{filtered.length === 0 ? (
{memQ.isLoading ? "불러오는 중…" : "구성원이 없습니다"}
) : filtered.map((o) => ( ))}
)}
); }